diff --git a/README.md b/README.md index 7eaf5ebb..78c4a3ec 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ php bin/generate-llms-full - `bin/generate-feed` rewrites `public/feed.xml` from the published posts in the database. - `bin/sitemap` rewrites `public/sitemap.xml` from the published posts in the database. -- `bin/generate-llms-full` rewrites `public/llms-full.txt` by concatenating `public/md-articles/index.md` and every other `public/md-articles/*/*.md` file, sorted by path. Requires the `llms.sourceDir` / `llms.outputFile` keys in `config/autoload/local.php` (see `local.php.dist`). +- `bin/generate-llms-full` rewrites `public/llms-full.txt` by concatenating `public/md-articles/index.md` and every other `public/md-articles/*/*.md` file, sorted by path, then appending each `public/md-pages/*.md` file — the markdown versions of the static pages — labelled with a `md-pages/` prefix in the section header. Requires the `llms.sourceDir` / `llms.outputFile` keys in `config/autoload/local.php` (see `local.php.dist`); the `llms.pagesDir` key is optional, and omitting it leaves the page sections out. These three have no ordering dependency on each other, only on step 3 being done first. @@ -75,4 +75,4 @@ None of this is wired into an automated deploy pipeline in this repository — t - Logs to `log/generate-packages.log`. - **`bin/generate-feed`** (RSS, `public/feed.xml`) — **manual only**, no cron. Run it as part of the publish flow in step 4, right after `bin/doctrine-fixtures`/`bin/create-uploads-dir`, whenever an article is added, edited, or its `post_status` changes. - **`bin/sitemap`** (`public/sitemap.xml`) — **manual only**, no cron. Same trigger as `bin/generate-feed`: re-run after any change to `articles_cleaned.json`. -- **`bin/generate-llms-full`** (`public/llms-full.txt`) — **manual only**, no cron. Re-run after adding/editing a `.md` file under `public/md-articles/`, or after removing/renaming a draft's `.md` file (it doesn't check `post_status`, see step 1). +- **`bin/generate-llms-full`** (`public/llms-full.txt`) — **manual only**, no cron. Re-run after adding/editing a `.md` file under `public/md-articles/` or `public/md-pages/`, or after removing/renaming a draft's `.md` file (it doesn't check `post_status`, see step 1). The `public/md-pages/*.md` files are hand-maintained alongside the templates in `src/Page/templates/page/` — editing a page template means updating its `.md` and re-running this. diff --git a/bin/generate-llms-full b/bin/generate-llms-full index a42ac3ff..78acb030 100755 --- a/bin/generate-llms-full +++ b/bin/generate-llms-full @@ -15,7 +15,7 @@ $llmsFullGenerator = $container->get(LlmsFullGenerator::class); $count = $llmsFullGenerator->write(); printf( - "Done. %d article%s written to %s%s", + "Done. %d section%s written to %s%s", $count, $count === 1 ? '' : 's', $llmsFullGenerator->getOutputFile(), diff --git a/config/autoload/local.php.dist b/config/autoload/local.php.dist index 987bfed6..65a87766 100644 --- a/config/autoload/local.php.dist +++ b/config/autoload/local.php.dist @@ -22,9 +22,14 @@ $databases = [ ], ]; +/** + * Exposed twice below, deliberately: as the `application` config key that PHP reads, and as + * the `app` Twig global that templates read. `url` is the single application URL - the artifact + * generators take it from config, the JSON-LD templates build absolute URLs from `app.url`. + */ $app = [ - 'baseUrl' => $baseUrl, - 'meta' => [ + 'url' => $baseUrl, + 'meta' => [ 'title' => 'Dotkernel | Headless Platform for modern web application', 'description' => 'Dotkernel is a Headless Platform for building modern web applications ' . 'using a collection of applications (skeletons).', @@ -32,7 +37,6 @@ $app = [ 'type' => 'website', 'siteName' => 'Dotkernel Light', 'locale' => 'en_US', - 'url' => $baseUrl, 'twitterCard' => 'summary_large_image', 'twitterSite' => '@dotkernel', ], @@ -62,6 +66,8 @@ return [ 'llms' => [ 'sourceDir' => realpath(__DIR__ . '/../../public/md-articles'), 'outputFile' => realpath(__DIR__ . '/../../public/llms-full.txt'), + // Markdown versions of the static pages, appended after the articles. Optional. + 'pagesDir' => realpath(__DIR__ . '/../../public/md-pages'), ], /** * Credentials for `bin/generate-packages`. Reading public repositories needs no token @@ -80,6 +86,8 @@ return [ // and is routed in Light\App\RoutesDelegator. Re-adding it here registers the path twice. 'routes' => [ 'page' => [ + // Linked from the footer on every page, so this route must stay registered. + 'about' => 'about', 'contact' => 'contact', 'api' => 'api', 'admin' => 'admin', diff --git a/public/css/app.css b/public/css/app.css index 7f37af4f..4d702bf8 100644 --- a/public/css/app.css +++ b/public/css/app.css @@ -1 +1 @@ -:root,[data-bs-theme=light]{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-black:#000;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-gray-100:#f8f9fa;--bs-gray-200:#e9ecef;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#adb5bd;--bs-gray-600:#6c757d;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-primary:#0d6efd;--bs-secondary:#6c757d;--bs-success:#198754;--bs-info:#0dcaf0;--bs-warning:#ffc107;--bs-danger:#dc3545;--bs-light:#f8f9fa;--bs-dark:#212529;--bs-primary-rgb:13, 110, 253;--bs-secondary-rgb:108, 117, 125;--bs-success-rgb:25, 135, 84;--bs-info-rgb:13, 202, 240;--bs-warning-rgb:255, 193, 7;--bs-danger-rgb:220, 53, 69;--bs-light-rgb:248, 249, 250;--bs-dark-rgb:33, 37, 41;--bs-primary-text-emphasis:#052c65;--bs-secondary-text-emphasis:#2b2f32;--bs-success-text-emphasis:#0a3622;--bs-info-text-emphasis:#055160;--bs-warning-text-emphasis:#664d03;--bs-danger-text-emphasis:#58151c;--bs-light-text-emphasis:#495057;--bs-dark-text-emphasis:#495057;--bs-primary-bg-subtle:#cfe2ff;--bs-secondary-bg-subtle:#e2e3e5;--bs-success-bg-subtle:#d1e7dd;--bs-info-bg-subtle:#cff4fc;--bs-warning-bg-subtle:#fff3cd;--bs-danger-bg-subtle:#f8d7da;--bs-light-bg-subtle:#fcfcfd;--bs-dark-bg-subtle:#ced4da;--bs-primary-border-subtle:#9ec5fe;--bs-secondary-border-subtle:#c4c8cb;--bs-success-border-subtle:#a3cfbb;--bs-info-border-subtle:#9eeaf9;--bs-warning-border-subtle:#ffe69c;--bs-danger-border-subtle:#f1aeb5;--bs-light-border-subtle:#e9ecef;--bs-dark-border-subtle:#adb5bd;--bs-white-rgb:255, 255, 255;--bs-black-rgb:0, 0, 0;--bs-font-sans-serif:system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--bs-font-monospace:SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--bs-gradient:linear-gradient(180deg, #ffffff26, #fff0);--bs-body-font-family:var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight:400;--bs-body-line-height:1.5;--bs-body-color:#212529;--bs-body-color-rgb:33, 37, 41;--bs-body-bg:#fff;--bs-body-bg-rgb:255, 255, 255;--bs-emphasis-color:#000;--bs-emphasis-color-rgb:0, 0, 0;--bs-secondary-color:#212529bf;--bs-secondary-color-rgb:33, 37, 41;--bs-secondary-bg:#e9ecef;--bs-secondary-bg-rgb:233, 236, 239;--bs-tertiary-color:#21252980;--bs-tertiary-color-rgb:33, 37, 41;--bs-tertiary-bg:#f8f9fa;--bs-tertiary-bg-rgb:248, 249, 250;--bs-heading-color:inherit;--bs-link-color:#0d6efd;--bs-link-color-rgb:13, 110, 253;--bs-link-decoration:underline;--bs-link-hover-color:#0a58ca;--bs-link-hover-color-rgb:10, 88, 202;--bs-code-color:#d63384;--bs-highlight-color:#212529;--bs-highlight-bg:#fff3cd;--bs-border-width:1px;--bs-border-style:solid;--bs-border-color:#dee2e6;--bs-border-color-translucent:#0000002d;--bs-border-radius:.375rem;--bs-border-radius-sm:.25rem;--bs-border-radius-lg:.5rem;--bs-border-radius-xl:1rem;--bs-border-radius-xxl:2rem;--bs-border-radius-2xl:var(--bs-border-radius-xxl);--bs-border-radius-pill:50rem;--bs-box-shadow:0 .5rem 1rem #00000026;--bs-box-shadow-sm:0 .125rem .25rem #00000013;--bs-box-shadow-lg:0 1rem 3rem #0000002d;--bs-box-shadow-inset:inset 0 1px 2px #00000013;--bs-focus-ring-width:.25rem;--bs-focus-ring-opacity:.25;--bs-focus-ring-color:#0d6efd40;--bs-form-valid-color:#198754;--bs-form-valid-border-color:#198754;--bs-form-invalid-color:#dc3545;--bs-form-invalid-border-color:#dc3545}[data-bs-theme=dark]{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;--bs-body-color:#dee2e6;--bs-body-color-rgb:222, 226, 230;--bs-body-bg:#212529;--bs-body-bg-rgb:33, 37, 41;--bs-emphasis-color:#fff;--bs-emphasis-color-rgb:255, 255, 255;--bs-secondary-color:#dee2e6bf;--bs-secondary-color-rgb:222, 226, 230;--bs-secondary-bg:#343a40;--bs-secondary-bg-rgb:52, 58, 64;--bs-tertiary-color:#dee2e680;--bs-tertiary-color-rgb:222, 226, 230;--bs-tertiary-bg:#2b3035;--bs-tertiary-bg-rgb:43, 48, 53;--bs-primary-text-emphasis:#6ea8fe;--bs-secondary-text-emphasis:#a7acb1;--bs-success-text-emphasis:#75b798;--bs-info-text-emphasis:#6edff6;--bs-warning-text-emphasis:#ffda6a;--bs-danger-text-emphasis:#ea868f;--bs-light-text-emphasis:#f8f9fa;--bs-dark-text-emphasis:#dee2e6;--bs-primary-bg-subtle:#031633;--bs-secondary-bg-subtle:#161719;--bs-success-bg-subtle:#051b11;--bs-info-bg-subtle:#032830;--bs-warning-bg-subtle:#332701;--bs-danger-bg-subtle:#2c0b0e;--bs-light-bg-subtle:#343a40;--bs-dark-bg-subtle:#1a1d20;--bs-primary-border-subtle:#084298;--bs-secondary-border-subtle:#41464b;--bs-success-border-subtle:#0f5132;--bs-info-border-subtle:#087990;--bs-warning-border-subtle:#997404;--bs-danger-border-subtle:#842029;--bs-light-border-subtle:#495057;--bs-dark-border-subtle:#343a40;--bs-heading-color:inherit;--bs-link-color:#6ea8fe;--bs-link-hover-color:#8bb9fe;--bs-link-color-rgb:110, 168, 254;--bs-link-hover-color-rgb:139, 185, 254;--bs-code-color:#e685b5;--bs-highlight-color:#dee2e6;--bs-highlight-bg:#664d03;--bs-border-color:#495057;--bs-border-color-translucent:#ffffff26;--bs-form-valid-color:#75b798;--bs-form-valid-border-color:#75b798;--bs-form-invalid-color:#ea868f;--bs-form-invalid-border-color:#ea868f}*,:before,:after{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:#0000;margin:0}hr{color:inherit;border:0;border-top:var(--bs-border-width) solid;opacity:.25;margin:1rem 0}h6,.h6,h5,.h5,h4,.h4,h3,.h3,h2,.h2,h1,.h1{color:var(--bs-heading-color);margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}h1,.h1{font-size:calc(1.375rem + 1.5vw)}@media (width>=1200px){h1,.h1{font-size:2.5rem}}h2,.h2{font-size:calc(1.325rem + .9vw)}@media (width>=1200px){h2,.h2{font-size:2rem}}h3,.h3{font-size:calc(1.3rem + .6vw)}@media (width>=1200px){h3,.h3{font-size:1.75rem}}h4,.h4{font-size:calc(1.275rem + .3vw)}@media (width>=1200px){h4,.h4{font-size:1.5rem}}h5,.h5{font-size:1.25rem}h6,.h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[title]{cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}address{font-style:normal;line-height:inherit;margin-bottom:1rem}ol,ul{padding-left:2rem}ol,ul,dl{margin-top:0;margin-bottom:1rem}ol ol,ul ul,ol ul,ul ol{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small,.small{font-size:.875em}mark,.mark{color:var(--bs-highlight-color);background-color:var(--bs-highlight-bg);padding:.1875em}sub,sup{vertical-align:baseline;font-size:.75em;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}a:hover{--bs-link-color-rgb:var(--bs-link-hover-color-rgb)}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}pre,code,kbd,samp{font-family:var(--bs-font-monospace);font-size:1em}pre{margin-top:0;margin-bottom:1rem;font-size:.875em;display:block;overflow:auto}pre code{font-size:inherit;color:inherit;word-break:normal}code{color:var(--bs-code-color);word-wrap:break-word;font-size:.875em}a>code{color:inherit}kbd{color:var(--bs-body-bg);background-color:var(--bs-body-color);border-radius:.25rem;padding:.1875rem .375rem;font-size:.875em}kbd kbd{padding:0;font-size:1em}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{color:var(--bs-secondary-color);text-align:left;padding-top:.5rem;padding-bottom:.5rem}th{text-align:inherit;text-align:-webkit-match-parent}thead,tbody,tfoot,tr,td,th{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}input,button,select,optgroup,textarea{font-family:inherit;font-size:inherit;line-height:inherit;margin:0}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator{display:none!important}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}button:not(:disabled),[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled){cursor:pointer}::-moz-focus-inner{border-style:none;padding:0}textarea{resize:vertical}fieldset{border:0;min-width:0;margin:0;padding:0}legend{float:left;width:100%;line-height:inherit;margin-bottom:.5rem;padding:0;font-size:calc(1.275rem + .3vw)}@media (width>=1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit-text{padding:0}::-webkit-datetime-edit-minute{padding:0}::-webkit-datetime-edit-hour-field{padding:0}::-webkit-datetime-edit-day-field{padding:0}::-webkit-datetime-edit-month-field{padding:0}::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button{cursor:pointer;filter:grayscale()}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{cursor:pointer;display:list-item}progress{vertical-align:baseline}[hidden]{display:none!important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media (width>=1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media (width>=1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media (width>=1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media (width>=1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media (width>=1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media (width>=1200px){.display-6{font-size:2.5rem}}.list-unstyled,.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{text-transform:uppercase;font-size:.875em}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{color:#6c757d;margin-top:-1rem;margin-bottom:1rem;font-size:.875em}.blockquote-footer:before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{background-color:var(--bs-body-bg);border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius);max-width:100%;height:auto;padding:.25rem}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{color:var(--bs-secondary-color);font-size:.875em}.container,.container-fluid,.container-xxl,.container-xl,.container-lg,.container-md,.container-sm{--bs-gutter-x:1.5rem;--bs-gutter-y:0;width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-left:auto;margin-right:auto}@media (width>=576px){.container-sm,.container{max-width:540px}}@media (width>=768px){.container-md,.container-sm,.container{max-width:720px}}@media (width>=992px){.container-lg,.container-md,.container-sm,.container{max-width:960px}}@media (width>=1200px){.container-xl,.container-lg,.container-md,.container-sm,.container{max-width:1140px}}@media (width>=1400px){.container-xxl,.container-xl,.container-lg,.container-md,.container-sm,.container{max-width:1320px}}:root{--bs-breakpoint-xs:0;--bs-breakpoint-sm:576px;--bs-breakpoint-md:768px;--bs-breakpoint-lg:992px;--bs-breakpoint-xl:1200px;--bs-breakpoint-xxl:1400px}.row{--bs-gutter-x:1.5rem;--bs-gutter-y:0;margin-top:calc(-1 * var(--bs-gutter-y));margin-right:calc(-.5 * var(--bs-gutter-x));margin-left:calc(-.5 * var(--bs-gutter-x));flex-wrap:wrap;display:flex}.row>*{width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-top:var(--bs-gutter-y);flex-shrink:0}.col{flex:1 0 0}.row-cols-auto>*{flex:none;width:auto}.row-cols-1>*{flex:none;width:100%}.row-cols-2>*{flex:none;width:50%}.row-cols-3>*{flex:none;width:33.3333%}.row-cols-4>*{flex:none;width:25%}.row-cols-5>*{flex:none;width:20%}.row-cols-6>*{flex:none;width:16.6667%}.col-auto{flex:none;width:auto}.col-1{flex:none;width:8.33333%}.col-2{flex:none;width:16.6667%}.col-3{flex:none;width:25%}.col-4{flex:none;width:33.3333%}.col-5{flex:none;width:41.6667%}.col-6{flex:none;width:50%}.col-7{flex:none;width:58.3333%}.col-8{flex:none;width:66.6667%}.col-9{flex:none;width:75%}.col-10{flex:none;width:83.3333%}.col-11{flex:none;width:91.6667%}.col-12{flex:none;width:100%}.offset-1{margin-left:8.33333%}.offset-2{margin-left:16.6667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.3333%}.offset-5{margin-left:41.6667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.3333%}.offset-8{margin-left:66.6667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.3333%}.offset-11{margin-left:91.6667%}.g-0,.gx-0{--bs-gutter-x:0}.g-0,.gy-0{--bs-gutter-y:0}.g-1,.gx-1{--bs-gutter-x:.25rem}.g-1,.gy-1{--bs-gutter-y:.25rem}.g-2,.gx-2{--bs-gutter-x:.5rem}.g-2,.gy-2{--bs-gutter-y:.5rem}.g-3,.gx-3{--bs-gutter-x:1rem}.g-3,.gy-3{--bs-gutter-y:1rem}.g-4,.gx-4{--bs-gutter-x:1.5rem}.g-4,.gy-4{--bs-gutter-y:1.5rem}.g-5,.gx-5{--bs-gutter-x:3rem}.g-5,.gy-5{--bs-gutter-y:3rem}@media (width>=576px){.col-sm{flex:1 0 0}.row-cols-sm-auto>*{flex:none;width:auto}.row-cols-sm-1>*{flex:none;width:100%}.row-cols-sm-2>*{flex:none;width:50%}.row-cols-sm-3>*{flex:none;width:33.3333%}.row-cols-sm-4>*{flex:none;width:25%}.row-cols-sm-5>*{flex:none;width:20%}.row-cols-sm-6>*{flex:none;width:16.6667%}.col-sm-auto{flex:none;width:auto}.col-sm-1{flex:none;width:8.33333%}.col-sm-2{flex:none;width:16.6667%}.col-sm-3{flex:none;width:25%}.col-sm-4{flex:none;width:33.3333%}.col-sm-5{flex:none;width:41.6667%}.col-sm-6{flex:none;width:50%}.col-sm-7{flex:none;width:58.3333%}.col-sm-8{flex:none;width:66.6667%}.col-sm-9{flex:none;width:75%}.col-sm-10{flex:none;width:83.3333%}.col-sm-11{flex:none;width:91.6667%}.col-sm-12{flex:none;width:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333%}.offset-sm-2{margin-left:16.6667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.3333%}.offset-sm-5{margin-left:41.6667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.3333%}.offset-sm-8{margin-left:66.6667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.3333%}.offset-sm-11{margin-left:91.6667%}.g-sm-0,.gx-sm-0{--bs-gutter-x:0}.g-sm-0,.gy-sm-0{--bs-gutter-y:0}.g-sm-1,.gx-sm-1{--bs-gutter-x:.25rem}.g-sm-1,.gy-sm-1{--bs-gutter-y:.25rem}.g-sm-2,.gx-sm-2{--bs-gutter-x:.5rem}.g-sm-2,.gy-sm-2{--bs-gutter-y:.5rem}.g-sm-3,.gx-sm-3{--bs-gutter-x:1rem}.g-sm-3,.gy-sm-3{--bs-gutter-y:1rem}.g-sm-4,.gx-sm-4{--bs-gutter-x:1.5rem}.g-sm-4,.gy-sm-4{--bs-gutter-y:1.5rem}.g-sm-5,.gx-sm-5{--bs-gutter-x:3rem}.g-sm-5,.gy-sm-5{--bs-gutter-y:3rem}}@media (width>=768px){.col-md{flex:1 0 0}.row-cols-md-auto>*{flex:none;width:auto}.row-cols-md-1>*{flex:none;width:100%}.row-cols-md-2>*{flex:none;width:50%}.row-cols-md-3>*{flex:none;width:33.3333%}.row-cols-md-4>*{flex:none;width:25%}.row-cols-md-5>*{flex:none;width:20%}.row-cols-md-6>*{flex:none;width:16.6667%}.col-md-auto{flex:none;width:auto}.col-md-1{flex:none;width:8.33333%}.col-md-2{flex:none;width:16.6667%}.col-md-3{flex:none;width:25%}.col-md-4{flex:none;width:33.3333%}.col-md-5{flex:none;width:41.6667%}.col-md-6{flex:none;width:50%}.col-md-7{flex:none;width:58.3333%}.col-md-8{flex:none;width:66.6667%}.col-md-9{flex:none;width:75%}.col-md-10{flex:none;width:83.3333%}.col-md-11{flex:none;width:91.6667%}.col-md-12{flex:none;width:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333%}.offset-md-2{margin-left:16.6667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.3333%}.offset-md-5{margin-left:41.6667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.3333%}.offset-md-8{margin-left:66.6667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.3333%}.offset-md-11{margin-left:91.6667%}.g-md-0,.gx-md-0{--bs-gutter-x:0}.g-md-0,.gy-md-0{--bs-gutter-y:0}.g-md-1,.gx-md-1{--bs-gutter-x:.25rem}.g-md-1,.gy-md-1{--bs-gutter-y:.25rem}.g-md-2,.gx-md-2{--bs-gutter-x:.5rem}.g-md-2,.gy-md-2{--bs-gutter-y:.5rem}.g-md-3,.gx-md-3{--bs-gutter-x:1rem}.g-md-3,.gy-md-3{--bs-gutter-y:1rem}.g-md-4,.gx-md-4{--bs-gutter-x:1.5rem}.g-md-4,.gy-md-4{--bs-gutter-y:1.5rem}.g-md-5,.gx-md-5{--bs-gutter-x:3rem}.g-md-5,.gy-md-5{--bs-gutter-y:3rem}}@media (width>=992px){.col-lg{flex:1 0 0}.row-cols-lg-auto>*{flex:none;width:auto}.row-cols-lg-1>*{flex:none;width:100%}.row-cols-lg-2>*{flex:none;width:50%}.row-cols-lg-3>*{flex:none;width:33.3333%}.row-cols-lg-4>*{flex:none;width:25%}.row-cols-lg-5>*{flex:none;width:20%}.row-cols-lg-6>*{flex:none;width:16.6667%}.col-lg-auto{flex:none;width:auto}.col-lg-1{flex:none;width:8.33333%}.col-lg-2{flex:none;width:16.6667%}.col-lg-3{flex:none;width:25%}.col-lg-4{flex:none;width:33.3333%}.col-lg-5{flex:none;width:41.6667%}.col-lg-6{flex:none;width:50%}.col-lg-7{flex:none;width:58.3333%}.col-lg-8{flex:none;width:66.6667%}.col-lg-9{flex:none;width:75%}.col-lg-10{flex:none;width:83.3333%}.col-lg-11{flex:none;width:91.6667%}.col-lg-12{flex:none;width:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333%}.offset-lg-2{margin-left:16.6667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.3333%}.offset-lg-5{margin-left:41.6667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.3333%}.offset-lg-8{margin-left:66.6667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.3333%}.offset-lg-11{margin-left:91.6667%}.g-lg-0,.gx-lg-0{--bs-gutter-x:0}.g-lg-0,.gy-lg-0{--bs-gutter-y:0}.g-lg-1,.gx-lg-1{--bs-gutter-x:.25rem}.g-lg-1,.gy-lg-1{--bs-gutter-y:.25rem}.g-lg-2,.gx-lg-2{--bs-gutter-x:.5rem}.g-lg-2,.gy-lg-2{--bs-gutter-y:.5rem}.g-lg-3,.gx-lg-3{--bs-gutter-x:1rem}.g-lg-3,.gy-lg-3{--bs-gutter-y:1rem}.g-lg-4,.gx-lg-4{--bs-gutter-x:1.5rem}.g-lg-4,.gy-lg-4{--bs-gutter-y:1.5rem}.g-lg-5,.gx-lg-5{--bs-gutter-x:3rem}.g-lg-5,.gy-lg-5{--bs-gutter-y:3rem}}@media (width>=1200px){.col-xl{flex:1 0 0}.row-cols-xl-auto>*{flex:none;width:auto}.row-cols-xl-1>*{flex:none;width:100%}.row-cols-xl-2>*{flex:none;width:50%}.row-cols-xl-3>*{flex:none;width:33.3333%}.row-cols-xl-4>*{flex:none;width:25%}.row-cols-xl-5>*{flex:none;width:20%}.row-cols-xl-6>*{flex:none;width:16.6667%}.col-xl-auto{flex:none;width:auto}.col-xl-1{flex:none;width:8.33333%}.col-xl-2{flex:none;width:16.6667%}.col-xl-3{flex:none;width:25%}.col-xl-4{flex:none;width:33.3333%}.col-xl-5{flex:none;width:41.6667%}.col-xl-6{flex:none;width:50%}.col-xl-7{flex:none;width:58.3333%}.col-xl-8{flex:none;width:66.6667%}.col-xl-9{flex:none;width:75%}.col-xl-10{flex:none;width:83.3333%}.col-xl-11{flex:none;width:91.6667%}.col-xl-12{flex:none;width:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333%}.offset-xl-2{margin-left:16.6667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.3333%}.offset-xl-5{margin-left:41.6667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.3333%}.offset-xl-8{margin-left:66.6667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.3333%}.offset-xl-11{margin-left:91.6667%}.g-xl-0,.gx-xl-0{--bs-gutter-x:0}.g-xl-0,.gy-xl-0{--bs-gutter-y:0}.g-xl-1,.gx-xl-1{--bs-gutter-x:.25rem}.g-xl-1,.gy-xl-1{--bs-gutter-y:.25rem}.g-xl-2,.gx-xl-2{--bs-gutter-x:.5rem}.g-xl-2,.gy-xl-2{--bs-gutter-y:.5rem}.g-xl-3,.gx-xl-3{--bs-gutter-x:1rem}.g-xl-3,.gy-xl-3{--bs-gutter-y:1rem}.g-xl-4,.gx-xl-4{--bs-gutter-x:1.5rem}.g-xl-4,.gy-xl-4{--bs-gutter-y:1.5rem}.g-xl-5,.gx-xl-5{--bs-gutter-x:3rem}.g-xl-5,.gy-xl-5{--bs-gutter-y:3rem}}@media (width>=1400px){.col-xxl{flex:1 0 0}.row-cols-xxl-auto>*{flex:none;width:auto}.row-cols-xxl-1>*{flex:none;width:100%}.row-cols-xxl-2>*{flex:none;width:50%}.row-cols-xxl-3>*{flex:none;width:33.3333%}.row-cols-xxl-4>*{flex:none;width:25%}.row-cols-xxl-5>*{flex:none;width:20%}.row-cols-xxl-6>*{flex:none;width:16.6667%}.col-xxl-auto{flex:none;width:auto}.col-xxl-1{flex:none;width:8.33333%}.col-xxl-2{flex:none;width:16.6667%}.col-xxl-3{flex:none;width:25%}.col-xxl-4{flex:none;width:33.3333%}.col-xxl-5{flex:none;width:41.6667%}.col-xxl-6{flex:none;width:50%}.col-xxl-7{flex:none;width:58.3333%}.col-xxl-8{flex:none;width:66.6667%}.col-xxl-9{flex:none;width:75%}.col-xxl-10{flex:none;width:83.3333%}.col-xxl-11{flex:none;width:91.6667%}.col-xxl-12{flex:none;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:8.33333%}.offset-xxl-2{margin-left:16.6667%}.offset-xxl-3{margin-left:25%}.offset-xxl-4{margin-left:33.3333%}.offset-xxl-5{margin-left:41.6667%}.offset-xxl-6{margin-left:50%}.offset-xxl-7{margin-left:58.3333%}.offset-xxl-8{margin-left:66.6667%}.offset-xxl-9{margin-left:75%}.offset-xxl-10{margin-left:83.3333%}.offset-xxl-11{margin-left:91.6667%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x:0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y:0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x:.25rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y:.25rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x:.5rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y:.5rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x:1rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y:1rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x:1.5rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y:1.5rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x:3rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y:3rem}}.table{--bs-table-color-type:initial;--bs-table-bg-type:initial;--bs-table-color-state:initial;--bs-table-bg-state:initial;--bs-table-color:var(--bs-emphasis-color);--bs-table-bg:var(--bs-body-bg);--bs-table-border-color:var(--bs-border-color);--bs-table-accent-bg:transparent;--bs-table-striped-color:var(--bs-emphasis-color);--bs-table-striped-bg:rgba(var(--bs-emphasis-color-rgb), .05);--bs-table-active-color:var(--bs-emphasis-color);--bs-table-active-bg:rgba(var(--bs-emphasis-color-rgb), .1);--bs-table-hover-color:var(--bs-emphasis-color);--bs-table-hover-bg:rgba(var(--bs-emphasis-color-rgb), .075);vertical-align:top;border-color:var(--bs-table-border-color);width:100%;margin-bottom:1rem}.table>:not(caption)>*>*{color:var(--bs-table-color-state,var(--bs-table-color-type,var(--bs-table-color)));background-color:var(--bs-table-bg);border-bottom-width:var(--bs-border-width);box-shadow:inset 0 0 0 9999px var(--bs-table-bg-state,var(--bs-table-bg-type,var(--bs-table-accent-bg)));padding:.5rem}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table-group-divider{border-top:calc(var(--bs-border-width) * 2) solid currentcolor}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem}.table-bordered>:not(caption)>*{border-width:var(--bs-border-width) 0}.table-bordered>:not(caption)>*>*{border-width:0 var(--bs-border-width)}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-borderless>:not(:first-child){border-top-width:0}.table-striped>tbody>tr:nth-of-type(odd)>*,.table-striped-columns>:not(caption)>tr>:nth-child(2n){--bs-table-color-type:var(--bs-table-striped-color);--bs-table-bg-type:var(--bs-table-striped-bg)}.table-active{--bs-table-color-state:var(--bs-table-active-color);--bs-table-bg-state:var(--bs-table-active-bg)}.table-hover>tbody>tr:hover>*{--bs-table-color-state:var(--bs-table-hover-color);--bs-table-bg-state:var(--bs-table-hover-bg)}.table-primary{--bs-table-color:#000;--bs-table-bg:#cfe2ff;--bs-table-border-color:#a5b5cc;--bs-table-striped-bg:#c4d7f2;--bs-table-striped-color:#000;--bs-table-active-bg:#bacbe5;--bs-table-active-color:#000;--bs-table-hover-bg:#bfd1ec;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-secondary{--bs-table-color:#000;--bs-table-bg:#e2e3e5;--bs-table-border-color:#b4b6b7;--bs-table-striped-bg:#d6d8da;--bs-table-striped-color:#000;--bs-table-active-bg:#cbcdce;--bs-table-active-color:#000;--bs-table-hover-bg:#d1d2d4;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-success{--bs-table-color:#000;--bs-table-bg:#d1e7dd;--bs-table-border-color:#a7b9b1;--bs-table-striped-bg:#c7dbd2;--bs-table-striped-color:#000;--bs-table-active-bg:#bcd0c7;--bs-table-active-color:#000;--bs-table-hover-bg:#c1d6cc;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-info{--bs-table-color:#000;--bs-table-bg:#cff4fc;--bs-table-border-color:#a5c4ca;--bs-table-striped-bg:#c4e8ef;--bs-table-striped-color:#000;--bs-table-active-bg:#badce3;--bs-table-active-color:#000;--bs-table-hover-bg:#bfe2e9;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-warning{--bs-table-color:#000;--bs-table-bg:#fff3cd;--bs-table-border-color:#ccc2a4;--bs-table-striped-bg:#f2e6c3;--bs-table-striped-color:#000;--bs-table-active-bg:#e6dab9;--bs-table-active-color:#000;--bs-table-hover-bg:#ece0be;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-danger{--bs-table-color:#000;--bs-table-bg:#f8d7da;--bs-table-border-color:#c6acae;--bs-table-striped-bg:#eccccf;--bs-table-striped-color:#000;--bs-table-active-bg:#dfc1c4;--bs-table-active-color:#000;--bs-table-hover-bg:#e5c7c9;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-light{--bs-table-color:#000;--bs-table-bg:#f8f9fa;--bs-table-border-color:#c6c7c8;--bs-table-striped-bg:#ecedee;--bs-table-striped-color:#000;--bs-table-active-bg:#dfe0e1;--bs-table-active-color:#000;--bs-table-hover-bg:#e5e6e7;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-dark{--bs-table-color:#fff;--bs-table-bg:#212529;--bs-table-border-color:#4d5154;--bs-table-striped-bg:#2c3034;--bs-table-striped-color:#fff;--bs-table-active-bg:#373b3e;--bs-table-active-color:#fff;--bs-table-hover-bg:#323539;--bs-table-hover-color:#fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-responsive{-webkit-overflow-scrolling:touch;overflow-x:auto}@media (width<=575.98px){.table-responsive-sm{-webkit-overflow-scrolling:touch;overflow-x:auto}}@media (width<=767.98px){.table-responsive-md{-webkit-overflow-scrolling:touch;overflow-x:auto}}@media (width<=991.98px){.table-responsive-lg{-webkit-overflow-scrolling:touch;overflow-x:auto}}@media (width<=1199.98px){.table-responsive-xl{-webkit-overflow-scrolling:touch;overflow-x:auto}}@media (width<=1399.98px){.table-responsive-xxl{-webkit-overflow-scrolling:touch;overflow-x:auto}}.form-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(.375rem + var(--bs-border-width));padding-bottom:calc(.375rem + var(--bs-border-width));font-size:inherit;margin-bottom:0;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + var(--bs-border-width));padding-bottom:calc(.5rem + var(--bs-border-width));font-size:1.25rem}.col-form-label-sm{padding-top:calc(.25rem + var(--bs-border-width));padding-bottom:calc(.25rem + var(--bs-border-width));font-size:.875rem}.form-text{color:var(--bs-secondary-color);margin-top:.25rem;font-size:.875em}.form-control{width:100%;color:var(--bs-body-color);appearance:none;background-color:var(--bs-body-bg);border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius);background-clip:padding-box;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;display:block}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:var(--bs-body-color);background-color:var(--bs-body-bg);border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem #0d6efd40}.form-control::-webkit-date-and-time-value{min-width:85px;height:1.5em;margin:0}.form-control::-webkit-datetime-edit{padding:0;display:block}.form-control::placeholder{color:var(--bs-secondary-color);opacity:1}.form-control:disabled{background-color:var(--bs-secondary-bg);opacity:1}.form-control::file-selector-button{margin:-.375rem -.75rem;color:var(--bs-body-color);background-color:var(--bs-tertiary-bg);pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:var(--bs-border-width);border-radius:0;margin-inline-end:.75rem;padding:.375rem .75rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:var(--bs-secondary-bg)}.form-control-plaintext{width:100%;color:var(--bs-body-color);border:solid #0000;border-width:var(--bs-border-width) 0;background-color:#0000;margin-bottom:0;padding:.375rem 0;line-height:1.5;display:block}.form-control-plaintext:focus{outline:0}.form-control-plaintext.form-control-sm,.form-control-plaintext.form-control-lg{padding-left:0;padding-right:0}.form-control-sm{min-height:calc(1.5em + .5rem + calc(var(--bs-border-width) * 2));border-radius:var(--bs-border-radius-sm);padding:.25rem .5rem;font-size:.875rem}.form-control-sm::file-selector-button{margin:-.25rem -.5rem;margin-inline-end:.5rem;padding:.25rem .5rem}.form-control-lg{min-height:calc(1.5em + 1rem + calc(var(--bs-border-width) * 2));border-radius:var(--bs-border-radius-lg);padding:.5rem 1rem;font-size:1.25rem}.form-control-lg::file-selector-button{margin:-.5rem -1rem;margin-inline-end:1rem;padding:.5rem 1rem}textarea.form-control{min-height:calc(1.5em + .75rem + calc(var(--bs-border-width) * 2))}textarea.form-control-sm{min-height:calc(1.5em + .5rem + calc(var(--bs-border-width) * 2))}textarea.form-control-lg{min-height:calc(1.5em + 1rem + calc(var(--bs-border-width) * 2))}.form-control-color{width:3rem;height:calc(1.5em + .75rem + calc(var(--bs-border-width) * 2));padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{border-radius:var(--bs-border-radius);border:0!important}.form-control-color::-webkit-color-swatch{border-radius:var(--bs-border-radius);border:0!important}.form-control-color.form-control-sm{height:calc(1.5em + .5rem + calc(var(--bs-border-width) * 2))}.form-control-color.form-control-lg{height:calc(1.5em + 1rem + calc(var(--bs-border-width) * 2))}.form-select{--bs-form-select-bg-img:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");width:100%;color:var(--bs-body-color);appearance:none;background-color:var(--bs-body-bg);background-image:var(--bs-form-select-bg-img), var(--bs-form-select-bg-icon,none);border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius);background-position:right .75rem center;background-repeat:no-repeat;background-size:16px 12px;padding:.375rem 2.25rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;display:block}@media (prefers-reduced-motion:reduce){.form-select{transition:none}}.form-select:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem #0d6efd40}.form-select[multiple],.form-select[size]:not([size="1"]){background-image:none;padding-right:.75rem}.form-select:disabled{background-color:var(--bs-secondary-bg)}.form-select:-moz-focusring{color:#0000;text-shadow:0 0 0 var(--bs-body-color)}.form-select-sm{border-radius:var(--bs-border-radius-sm);padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.form-select-lg{border-radius:var(--bs-border-radius-lg);padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}[data-bs-theme=dark] .form-select{--bs-form-select-bg-img:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23dee2e6' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e")}.form-check{min-height:1.5rem;margin-bottom:.125rem;padding-left:1.5em;display:block}.form-check .form-check-input{float:left;margin-left:-1.5em}.form-check-reverse{text-align:right;padding-left:0;padding-right:1.5em}.form-check-reverse .form-check-input{float:right;margin-left:0;margin-right:-1.5em}.form-check-input{--bs-form-check-bg:var(--bs-body-bg);vertical-align:top;appearance:none;background-color:var(--bs-form-check-bg);background-image:var(--bs-form-check-bg-image);border:var(--bs-border-width) solid var(--bs-border-color);-webkit-print-color-adjust:exact;print-color-adjust:exact;background-position:50%;background-repeat:no-repeat;background-size:contain;flex-shrink:0;width:1em;height:1em;margin-top:.25em}.form-check-input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio]{border-radius:50%}.form-check-input:active{filter:brightness(90%)}.form-check-input:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem #0d6efd40}.form-check-input:checked{background-color:#0d6efd;border-color:#0d6efd}.form-check-input:checked[type=checkbox]{--bs-form-check-bg-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio]{--bs-form-check-bg-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate{--bs-form-check-bg-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e");background-color:#0d6efd;border-color:#0d6efd}.form-check-input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input[disabled]~.form-check-label,.form-check-input:disabled~.form-check-label{cursor:default;opacity:.5}.form-switch{padding-left:2.5em}.form-switch .form-check-input{--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");background-image:var(--bs-form-switch-bg);background-position:0;border-radius:2em;width:2em;margin-left:-2.5em;transition:background-position .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgb%2852.5490196078%, 71.568627451%, 99.6078431373%%29'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e");background-position:100%}.form-switch.form-check-reverse{padding-left:0;padding-right:2.5em}.form-switch.form-check-reverse .form-check-input{margin-left:0;margin-right:-2.5em}.form-check-inline{margin-right:1rem;display:inline-block}.btn-check{clip:rect(0, 0, 0, 0);pointer-events:none;position:absolute}.btn-check[disabled]+.btn,.btn-check:disabled+.btn{pointer-events:none;filter:none;opacity:.65}[data-bs-theme=dark] .form-switch .form-check-input:not(:checked):not(:focus){--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%28255, 255, 255, 0.25%29'/%3e%3c/svg%3e")}.form-range{appearance:none;background-color:#0000;width:100%;height:1.5rem;padding:0}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem #0d6efd40}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem #0d6efd40}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{appearance:none;background-color:#0d6efd;border:0;border-radius:1rem;width:1rem;height:1rem;margin-top:-.25rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-range::-webkit-slider-thumb{transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#b6d4fe}.form-range::-webkit-slider-runnable-track{color:#0000;cursor:pointer;background-color:var(--bs-secondary-bg);border-color:#0000;border-radius:1rem;width:100%;height:.5rem}.form-range::-moz-range-thumb{appearance:none;background-color:#0d6efd;border:0;border-radius:1rem;width:1rem;height:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-range::-moz-range-thumb{transition:none}}.form-range::-moz-range-thumb:active{background-color:#b6d4fe}.form-range::-moz-range-track{color:#0000;cursor:pointer;background-color:var(--bs-secondary-bg);border-color:#0000;border-radius:1rem;width:100%;height:.5rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:var(--bs-secondary-color)}.form-range:disabled::-moz-range-thumb{background-color:var(--bs-secondary-color)}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-control-plaintext,.form-floating>.form-select{height:calc(3.5rem + calc(var(--bs-border-width) * 2));min-height:calc(3.5rem + calc(var(--bs-border-width) * 2));line-height:1.25}.form-floating>label{z-index:2;max-width:100%;height:100%;color:rgba(var(--bs-body-color-rgb), .65);text-align:start;text-overflow:ellipsis;white-space:nowrap;pointer-events:none;border:var(--bs-border-width) solid transparent;transform-origin:0 0;padding:1rem .75rem;transition:opacity .1s ease-in-out,transform .1s ease-in-out;position:absolute;top:0;left:0;overflow:hidden}@media (prefers-reduced-motion:reduce){.form-floating>label{transition:none}}.form-floating>.form-control,.form-floating>.form-control-plaintext{padding:1rem .75rem}.form-floating>.form-control::placeholder,.form-floating>.form-control-plaintext::placeholder{color:#0000}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown),.form-floating>.form-control-plaintext:focus,.form-floating>.form-control-plaintext:not(:placeholder-shown),:-webkit-any(.form-floating>.form-control:-webkit-autofill,.form-floating>.form-control-plaintext:-webkit-autofill){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown),.form-floating>.form-control-plaintext:focus,.form-floating>.form-control-plaintext:not(:placeholder-shown),:is(.form-floating>.form-control:autofill,.form-floating>.form-control-plaintext:autofill){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem;padding-left:.75rem}.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-control-plaintext~label,.form-floating>.form-select~label{transform:scale(.85)translateY(-.5rem)translate(.15rem)}.form-floating>.form-control:-webkit-autofill~label{transform:scale(.85)translateY(-.5rem)translate(.15rem)}.form-floating>textarea:focus~label:after,.form-floating>textarea:not(:placeholder-shown)~label:after{z-index:-1;content:"";background-color:var(--bs-body-bg);border-radius:var(--bs-border-radius);height:1.5em;position:absolute;inset:1rem .375rem}.form-floating>textarea:disabled~label:after{background-color:var(--bs-secondary-bg)}.form-floating>.form-control-plaintext~label{border-width:var(--bs-border-width) 0}.form-floating>:disabled~label,.form-floating>.form-control:disabled~label{color:#6c757d}.input-group{flex-wrap:wrap;align-items:stretch;width:100%;display:flex;position:relative}.input-group>.form-control,.input-group>.form-select,.input-group>.form-floating{flex:auto;width:1%;min-width:0;position:relative}.input-group>.form-control:focus,.input-group>.form-select:focus,.input-group>.form-floating:focus-within{z-index:5}.input-group .btn{z-index:2;position:relative}.input-group .btn:focus{z-index:5}.input-group-text{color:var(--bs-body-color);text-align:center;white-space:nowrap;background-color:var(--bs-tertiary-bg);border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius);align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;display:flex}.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text,.input-group-lg>.btn{border-radius:var(--bs-border-radius-lg);padding:.5rem 1rem;font-size:1.25rem}.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text,.input-group-sm>.btn{border-radius:var(--bs-border-radius-sm);padding:.25rem .5rem;font-size:.875rem}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating),.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3),.input-group:not(.has-validation)>.form-floating:not(:last-child)>.form-control,.input-group:not(.has-validation)>.form-floating:not(:last-child)>.form-select,.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating),.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4),.input-group.has-validation>.form-floating:nth-last-child(n+3)>.form-control,.input-group.has-validation>.form-floating:nth-last-child(n+3)>.form-select{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:calc(-1 * var(--bs-border-width));border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.form-floating:not(:first-child)>.form-control,.input-group>.form-floating:not(:first-child)>.form-select{border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{width:100%;color:var(--bs-form-valid-color);margin-top:.25rem;font-size:.875em;display:none}.valid-tooltip{z-index:5;color:#fff;background-color:var(--bs-success);border-radius:var(--bs-border-radius);max-width:100%;margin-top:.1rem;padding:.25rem .5rem;font-size:.875rem;display:none;position:absolute;top:100%}.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip,.is-valid~.valid-feedback,.is-valid~.valid-tooltip{display:block}.was-validated .form-control:valid,.form-control.is-valid{border-color:var(--bs-form-valid-border-color);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1'/%3e%3c/svg%3e");background-position:right calc(.375em + .1875rem) center;background-repeat:no-repeat;background-size:calc(.75em + .375rem) calc(.75em + .375rem);padding-right:calc(1.5em + .75rem)}.was-validated .form-control:valid:focus,.form-control.is-valid:focus{border-color:var(--bs-form-valid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-success-rgb), .25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{background-position:right calc(.375em + .1875rem) top calc(.375em + .1875rem);padding-right:calc(1.5em + .75rem)}.was-validated .form-select:valid,.form-select.is-valid{border-color:var(--bs-form-valid-border-color)}.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"],.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"]{--bs-form-select-bg-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1'/%3e%3c/svg%3e");background-position:right .75rem center,right 2.25rem center;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem);padding-right:4.125rem}.was-validated .form-select:valid:focus,.form-select.is-valid:focus{border-color:var(--bs-form-valid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-success-rgb), .25)}.was-validated .form-control-color:valid,.form-control-color.is-valid{width:calc(1.5em + 3.75rem)}.was-validated .form-check-input:valid,.form-check-input.is-valid{border-color:var(--bs-form-valid-border-color)}.was-validated .form-check-input:valid:checked,.form-check-input.is-valid:checked{background-color:var(--bs-form-valid-color)}.was-validated .form-check-input:valid:focus,.form-check-input.is-valid:focus{box-shadow:0 0 0 .25rem rgba(var(--bs-success-rgb), .25)}.was-validated .form-check-input:valid~.form-check-label,.form-check-input.is-valid~.form-check-label{color:var(--bs-form-valid-color)}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.was-validated .input-group>.form-control:not(:focus):valid,.input-group>.form-control:not(:focus).is-valid,.was-validated .input-group>.form-select:not(:focus):valid,.input-group>.form-select:not(:focus).is-valid,.was-validated .input-group>.form-floating:not(:focus-within):valid,.input-group>.form-floating:not(:focus-within).is-valid{z-index:3}.invalid-feedback{width:100%;color:var(--bs-form-invalid-color);margin-top:.25rem;font-size:.875em;display:none}.invalid-tooltip{z-index:5;color:#fff;background-color:var(--bs-danger);border-radius:var(--bs-border-radius);max-width:100%;margin-top:.1rem;padding:.25rem .5rem;font-size:.875rem;display:none;position:absolute;top:100%}.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip,.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip{display:block}.was-validated .form-control:invalid,.form-control.is-invalid{border-color:var(--bs-form-invalid-border-color);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-position:right calc(.375em + .1875rem) center;background-repeat:no-repeat;background-size:calc(.75em + .375rem) calc(.75em + .375rem);padding-right:calc(1.5em + .75rem)}.was-validated .form-control:invalid:focus,.form-control.is-invalid:focus{border-color:var(--bs-form-invalid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-danger-rgb), .25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{background-position:right calc(.375em + .1875rem) top calc(.375em + .1875rem);padding-right:calc(1.5em + .75rem)}.was-validated .form-select:invalid,.form-select.is-invalid{border-color:var(--bs-form-invalid-border-color)}.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"],.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"]{--bs-form-select-bg-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-position:right .75rem center,right 2.25rem center;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem);padding-right:4.125rem}.was-validated .form-select:invalid:focus,.form-select.is-invalid:focus{border-color:var(--bs-form-invalid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-danger-rgb), .25)}.was-validated .form-control-color:invalid,.form-control-color.is-invalid{width:calc(1.5em + 3.75rem)}.was-validated .form-check-input:invalid,.form-check-input.is-invalid{border-color:var(--bs-form-invalid-border-color)}.was-validated .form-check-input:invalid:checked,.form-check-input.is-invalid:checked{background-color:var(--bs-form-invalid-color)}.was-validated .form-check-input:invalid:focus,.form-check-input.is-invalid:focus{box-shadow:0 0 0 .25rem rgba(var(--bs-danger-rgb), .25)}.was-validated .form-check-input:invalid~.form-check-label,.form-check-input.is-invalid~.form-check-label{color:var(--bs-form-invalid-color)}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.was-validated .input-group>.form-control:not(:focus):invalid,.input-group>.form-control:not(:focus).is-invalid,.was-validated .input-group>.form-select:not(:focus):invalid,.input-group>.form-select:not(:focus).is-invalid,.was-validated .input-group>.form-floating:not(:focus-within):invalid,.input-group>.form-floating:not(:focus-within).is-invalid{z-index:4}.btn{--bs-btn-padding-x:.75rem;--bs-btn-padding-y:.375rem;--bs-btn-font-family: ;--bs-btn-font-size:1rem;--bs-btn-font-weight:400;--bs-btn-line-height:1.5;--bs-btn-color:var(--bs-body-color);--bs-btn-bg:transparent;--bs-btn-border-width:var(--bs-border-width);--bs-btn-border-color:transparent;--bs-btn-border-radius:var(--bs-border-radius);--bs-btn-hover-border-color:transparent;--bs-btn-box-shadow:inset 0 1px 0 #ffffff26, 0 1px 1px #00000013;--bs-btn-disabled-opacity:.65;--bs-btn-focus-box-shadow:0 0 0 .25rem rgba(var(--bs-btn-focus-shadow-rgb), .5);padding:var(--bs-btn-padding-y) var(--bs-btn-padding-x);font-family:var(--bs-btn-font-family);font-size:var(--bs-btn-font-size);font-weight:var(--bs-btn-font-weight);line-height:var(--bs-btn-line-height);color:var(--bs-btn-color);text-align:center;vertical-align:middle;cursor:pointer;-webkit-user-select:none;user-select:none;border:var(--bs-btn-border-width) solid var(--bs-btn-border-color);border-radius:var(--bs-btn-border-radius);background-color:var(--bs-btn-bg);text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;display:inline-block}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:var(--bs-btn-hover-color);background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color)}.btn-check+.btn:hover{color:var(--bs-btn-color);background-color:var(--bs-btn-bg);border-color:var(--bs-btn-border-color)}.btn:focus-visible{color:var(--bs-btn-hover-color);background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color);box-shadow:var(--bs-btn-focus-box-shadow);outline:0}.btn-check:focus-visible+.btn{border-color:var(--bs-btn-hover-border-color);box-shadow:var(--bs-btn-focus-box-shadow);outline:0}.btn-check:checked+.btn,:not(.btn-check)+.btn:active,.btn:first-child:active,.btn.active,.btn.show{color:var(--bs-btn-active-color);background-color:var(--bs-btn-active-bg);border-color:var(--bs-btn-active-border-color)}.btn-check:checked+.btn:focus-visible,:not(.btn-check)+.btn:active:focus-visible,.btn:first-child:active:focus-visible,.btn.active:focus-visible,.btn.show:focus-visible,.btn-check:checked:focus-visible+.btn{box-shadow:var(--bs-btn-focus-box-shadow)}.btn:disabled,.btn.disabled,fieldset:disabled .btn{color:var(--bs-btn-disabled-color);pointer-events:none;background-color:var(--bs-btn-disabled-bg);border-color:var(--bs-btn-disabled-border-color);opacity:var(--bs-btn-disabled-opacity)}.btn-primary{--bs-btn-color:#fff;--bs-btn-bg:#0d6efd;--bs-btn-border-color:#0d6efd;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#0b5ed7;--bs-btn-hover-border-color:#0a58ca;--bs-btn-focus-shadow-rgb:49, 132, 253;--bs-btn-active-color:#fff;--bs-btn-active-bg:#0a58ca;--bs-btn-active-border-color:#0a53be;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#0d6efd;--bs-btn-disabled-border-color:#0d6efd}.btn-secondary{--bs-btn-color:#fff;--bs-btn-bg:#6c757d;--bs-btn-border-color:#6c757d;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#5c636a;--bs-btn-hover-border-color:#565e64;--bs-btn-focus-shadow-rgb:130, 138, 145;--bs-btn-active-color:#fff;--bs-btn-active-bg:#565e64;--bs-btn-active-border-color:#51585e;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#6c757d;--bs-btn-disabled-border-color:#6c757d}.btn-success{--bs-btn-color:#fff;--bs-btn-bg:#198754;--bs-btn-border-color:#198754;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#157347;--bs-btn-hover-border-color:#146c43;--bs-btn-focus-shadow-rgb:60, 153, 110;--bs-btn-active-color:#fff;--bs-btn-active-bg:#146c43;--bs-btn-active-border-color:#13653f;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#198754;--bs-btn-disabled-border-color:#198754}.btn-info{--bs-btn-color:#000;--bs-btn-bg:#0dcaf0;--bs-btn-border-color:#0dcaf0;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#31d2f2;--bs-btn-hover-border-color:#25cff2;--bs-btn-focus-shadow-rgb:11, 172, 204;--bs-btn-active-color:#000;--bs-btn-active-bg:#3dd5f3;--bs-btn-active-border-color:#25cff2;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#000;--bs-btn-disabled-bg:#0dcaf0;--bs-btn-disabled-border-color:#0dcaf0}.btn-warning{--bs-btn-color:#000;--bs-btn-bg:#ffc107;--bs-btn-border-color:#ffc107;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#ffca2c;--bs-btn-hover-border-color:#ffc720;--bs-btn-focus-shadow-rgb:217, 164, 6;--bs-btn-active-color:#000;--bs-btn-active-bg:#ffcd39;--bs-btn-active-border-color:#ffc720;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#000;--bs-btn-disabled-bg:#ffc107;--bs-btn-disabled-border-color:#ffc107}.btn-danger{--bs-btn-color:#fff;--bs-btn-bg:#dc3545;--bs-btn-border-color:#dc3545;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#bb2d3b;--bs-btn-hover-border-color:#b02a37;--bs-btn-focus-shadow-rgb:225, 83, 97;--bs-btn-active-color:#fff;--bs-btn-active-bg:#b02a37;--bs-btn-active-border-color:#a52834;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#dc3545;--bs-btn-disabled-border-color:#dc3545}.btn-light{--bs-btn-color:#000;--bs-btn-bg:#f8f9fa;--bs-btn-border-color:#f8f9fa;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#d3d4d5;--bs-btn-hover-border-color:#c6c7c8;--bs-btn-focus-shadow-rgb:211, 212, 213;--bs-btn-active-color:#000;--bs-btn-active-bg:#c6c7c8;--bs-btn-active-border-color:#babbbc;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#000;--bs-btn-disabled-bg:#f8f9fa;--bs-btn-disabled-border-color:#f8f9fa}.btn-dark{--bs-btn-color:#fff;--bs-btn-bg:#212529;--bs-btn-border-color:#212529;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#424649;--bs-btn-hover-border-color:#373b3e;--bs-btn-focus-shadow-rgb:66, 70, 73;--bs-btn-active-color:#fff;--bs-btn-active-bg:#4d5154;--bs-btn-active-border-color:#373b3e;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#212529;--bs-btn-disabled-border-color:#212529}.btn-outline-primary{--bs-btn-color:#0d6efd;--bs-btn-border-color:#0d6efd;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#0d6efd;--bs-btn-hover-border-color:#0d6efd;--bs-btn-focus-shadow-rgb:13, 110, 253;--bs-btn-active-color:#fff;--bs-btn-active-bg:#0d6efd;--bs-btn-active-border-color:#0d6efd;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#0d6efd;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#0d6efd;--bs-gradient:none}.btn-outline-secondary{--bs-btn-color:#6c757d;--bs-btn-border-color:#6c757d;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#6c757d;--bs-btn-hover-border-color:#6c757d;--bs-btn-focus-shadow-rgb:108, 117, 125;--bs-btn-active-color:#fff;--bs-btn-active-bg:#6c757d;--bs-btn-active-border-color:#6c757d;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#6c757d;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#6c757d;--bs-gradient:none}.btn-outline-success{--bs-btn-color:#198754;--bs-btn-border-color:#198754;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#198754;--bs-btn-hover-border-color:#198754;--bs-btn-focus-shadow-rgb:25, 135, 84;--bs-btn-active-color:#fff;--bs-btn-active-bg:#198754;--bs-btn-active-border-color:#198754;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#198754;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#198754;--bs-gradient:none}.btn-outline-info{--bs-btn-color:#0dcaf0;--bs-btn-border-color:#0dcaf0;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#0dcaf0;--bs-btn-hover-border-color:#0dcaf0;--bs-btn-focus-shadow-rgb:13, 202, 240;--bs-btn-active-color:#000;--bs-btn-active-bg:#0dcaf0;--bs-btn-active-border-color:#0dcaf0;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#0dcaf0;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#0dcaf0;--bs-gradient:none}.btn-outline-warning{--bs-btn-color:#ffc107;--bs-btn-border-color:#ffc107;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#ffc107;--bs-btn-hover-border-color:#ffc107;--bs-btn-focus-shadow-rgb:255, 193, 7;--bs-btn-active-color:#000;--bs-btn-active-bg:#ffc107;--bs-btn-active-border-color:#ffc107;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#ffc107;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#ffc107;--bs-gradient:none}.btn-outline-danger{--bs-btn-color:#dc3545;--bs-btn-border-color:#dc3545;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#dc3545;--bs-btn-hover-border-color:#dc3545;--bs-btn-focus-shadow-rgb:220, 53, 69;--bs-btn-active-color:#fff;--bs-btn-active-bg:#dc3545;--bs-btn-active-border-color:#dc3545;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#dc3545;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#dc3545;--bs-gradient:none}.btn-outline-light{--bs-btn-color:#f8f9fa;--bs-btn-border-color:#f8f9fa;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#f8f9fa;--bs-btn-hover-border-color:#f8f9fa;--bs-btn-focus-shadow-rgb:248, 249, 250;--bs-btn-active-color:#000;--bs-btn-active-bg:#f8f9fa;--bs-btn-active-border-color:#f8f9fa;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#f8f9fa;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#f8f9fa;--bs-gradient:none}.btn-outline-dark{--bs-btn-color:#212529;--bs-btn-border-color:#212529;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#212529;--bs-btn-hover-border-color:#212529;--bs-btn-focus-shadow-rgb:33, 37, 41;--bs-btn-active-color:#fff;--bs-btn-active-bg:#212529;--bs-btn-active-border-color:#212529;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#212529;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#212529;--bs-gradient:none}.btn-link{--bs-btn-font-weight:400;--bs-btn-color:var(--bs-link-color);--bs-btn-bg:transparent;--bs-btn-border-color:transparent;--bs-btn-hover-color:var(--bs-link-hover-color);--bs-btn-hover-border-color:transparent;--bs-btn-active-color:var(--bs-link-hover-color);--bs-btn-active-border-color:transparent;--bs-btn-disabled-color:#6c757d;--bs-btn-disabled-border-color:transparent;--bs-btn-box-shadow:0 0 0 #000;--bs-btn-focus-shadow-rgb:49, 132, 253;text-decoration:underline}.btn-link:focus-visible{color:var(--bs-btn-color)}.btn-link:hover{color:var(--bs-btn-hover-color)}.btn-lg,.btn-group-lg>.btn{--bs-btn-padding-y:.5rem;--bs-btn-padding-x:1rem;--bs-btn-font-size:1.25rem;--bs-btn-border-radius:var(--bs-border-radius-lg)}.btn-sm,.btn-group-sm>.btn{--bs-btn-padding-y:.25rem;--bs-btn-padding-x:.5rem;--bs-btn-font-size:.875rem;--bs-btn-border-radius:var(--bs-border-radius-sm)}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;transition:height .35s;overflow:hidden}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width .35s}@media (prefers-reduced-motion:reduce){.collapsing.collapse-horizontal{transition:none}}.dropup,.dropend,.dropdown,.dropstart,.dropup-center,.dropdown-center{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle:after{vertical-align:.255em;content:"";border:.3em solid #0000;border-top-color:currentColor;border-bottom:0;margin-left:.255em;display:inline-block}.dropdown-toggle:empty:after{margin-left:0}.dropdown-menu{--bs-dropdown-zindex:1000;--bs-dropdown-min-width:10rem;--bs-dropdown-padding-x:0;--bs-dropdown-padding-y:.5rem;--bs-dropdown-spacer:.125rem;--bs-dropdown-font-size:1rem;--bs-dropdown-color:var(--bs-body-color);--bs-dropdown-bg:var(--bs-body-bg);--bs-dropdown-border-color:var(--bs-border-color-translucent);--bs-dropdown-border-radius:var(--bs-border-radius);--bs-dropdown-border-width:var(--bs-border-width);--bs-dropdown-inner-border-radius:calc(var(--bs-border-radius) - var(--bs-border-width));--bs-dropdown-divider-bg:var(--bs-border-color-translucent);--bs-dropdown-divider-margin-y:.5rem;--bs-dropdown-box-shadow:var(--bs-box-shadow);--bs-dropdown-link-color:var(--bs-body-color);--bs-dropdown-link-hover-color:var(--bs-body-color);--bs-dropdown-link-hover-bg:var(--bs-tertiary-bg);--bs-dropdown-link-active-color:#fff;--bs-dropdown-link-active-bg:#0d6efd;--bs-dropdown-link-disabled-color:var(--bs-tertiary-color);--bs-dropdown-item-padding-x:1rem;--bs-dropdown-item-padding-y:.25rem;--bs-dropdown-header-color:#6c757d;--bs-dropdown-header-padding-x:1rem;--bs-dropdown-header-padding-y:.5rem;z-index:var(--bs-dropdown-zindex);min-width:var(--bs-dropdown-min-width);padding:var(--bs-dropdown-padding-y) var(--bs-dropdown-padding-x);font-size:var(--bs-dropdown-font-size);color:var(--bs-dropdown-color);text-align:left;background-color:var(--bs-dropdown-bg);border:var(--bs-dropdown-border-width) solid var(--bs-dropdown-border-color);border-radius:var(--bs-dropdown-border-radius);background-clip:padding-box;margin:0;list-style:none;display:none;position:absolute}.dropdown-menu[data-bs-popper]{margin-top:var(--bs-dropdown-spacer);top:100%;left:0}.dropdown-menu-start{--bs-position:start}.dropdown-menu-start[data-bs-popper]{left:0;right:auto}.dropdown-menu-end{--bs-position:end}.dropdown-menu-end[data-bs-popper]{left:auto;right:0}@media (width>=576px){.dropdown-menu-sm-start{--bs-position:start}.dropdown-menu-sm-start[data-bs-popper]{left:0;right:auto}.dropdown-menu-sm-end{--bs-position:end}.dropdown-menu-sm-end[data-bs-popper]{left:auto;right:0}}@media (width>=768px){.dropdown-menu-md-start{--bs-position:start}.dropdown-menu-md-start[data-bs-popper]{left:0;right:auto}.dropdown-menu-md-end{--bs-position:end}.dropdown-menu-md-end[data-bs-popper]{left:auto;right:0}}@media (width>=992px){.dropdown-menu-lg-start{--bs-position:start}.dropdown-menu-lg-start[data-bs-popper]{left:0;right:auto}.dropdown-menu-lg-end{--bs-position:end}.dropdown-menu-lg-end[data-bs-popper]{left:auto;right:0}}@media (width>=1200px){.dropdown-menu-xl-start{--bs-position:start}.dropdown-menu-xl-start[data-bs-popper]{left:0;right:auto}.dropdown-menu-xl-end{--bs-position:end}.dropdown-menu-xl-end[data-bs-popper]{left:auto;right:0}}@media (width>=1400px){.dropdown-menu-xxl-start{--bs-position:start}.dropdown-menu-xxl-start[data-bs-popper]{left:0;right:auto}.dropdown-menu-xxl-end{--bs-position:end}.dropdown-menu-xxl-end[data-bs-popper]{left:auto;right:0}}.dropup .dropdown-menu[data-bs-popper]{margin-top:0;margin-bottom:var(--bs-dropdown-spacer);top:auto;bottom:100%}.dropup .dropdown-toggle:after{vertical-align:.255em;content:"";border:.3em solid #0000;border-top:0;border-bottom-color:currentColor;margin-left:.255em;display:inline-block}.dropup .dropdown-toggle:empty:after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{margin-top:0;margin-left:var(--bs-dropdown-spacer);top:0;left:100%;right:auto}.dropend .dropdown-toggle:after{vertical-align:.255em;content:"";border:.3em solid #0000;border-left-color:currentColor;border-right:0;margin-left:.255em;display:inline-block}.dropend .dropdown-toggle:empty:after{margin-left:0}.dropend .dropdown-toggle:after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{margin-top:0;margin-right:var(--bs-dropdown-spacer);top:0;left:auto;right:100%}.dropstart .dropdown-toggle:after{vertical-align:.255em;content:"";margin-left:.255em;display:none}.dropstart .dropdown-toggle:before{vertical-align:.255em;content:"";border-top:.3em solid #0000;border-bottom:.3em solid #0000;border-right:.3em solid;margin-right:.255em;display:inline-block}.dropstart .dropdown-toggle:empty:after{margin-left:0}.dropstart .dropdown-toggle:before{vertical-align:0}.dropdown-divider{height:0;margin:var(--bs-dropdown-divider-margin-y) 0;border-top:1px solid var(--bs-dropdown-divider-bg);opacity:1;overflow:hidden}.dropdown-item{width:100%;padding:var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);clear:both;color:var(--bs-dropdown-link-color);text-align:inherit;white-space:nowrap;border-radius:var(--bs-dropdown-item-border-radius,0);background-color:#0000;border:0;font-weight:400;text-decoration:none;display:block}.dropdown-item:hover,.dropdown-item:focus{color:var(--bs-dropdown-link-hover-color);background-color:var(--bs-dropdown-link-hover-bg)}.dropdown-item.active,.dropdown-item:active{color:var(--bs-dropdown-link-active-color);background-color:var(--bs-dropdown-link-active-bg);text-decoration:none}.dropdown-item.disabled,.dropdown-item:disabled{color:var(--bs-dropdown-link-disabled-color);pointer-events:none;background-color:#0000}.dropdown-menu.show{display:block}.dropdown-header{padding:var(--bs-dropdown-header-padding-y) var(--bs-dropdown-header-padding-x);color:var(--bs-dropdown-header-color);white-space:nowrap;margin-bottom:0;font-size:.875rem;display:block}.dropdown-item-text{padding:var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);color:var(--bs-dropdown-link-color);display:block}.dropdown-menu-dark{--bs-dropdown-color:#dee2e6;--bs-dropdown-bg:#343a40;--bs-dropdown-border-color:var(--bs-border-color-translucent);--bs-dropdown-box-shadow: ;--bs-dropdown-link-color:#dee2e6;--bs-dropdown-link-hover-color:#fff;--bs-dropdown-divider-bg:var(--bs-border-color-translucent);--bs-dropdown-link-hover-bg:#ffffff26;--bs-dropdown-link-active-color:#fff;--bs-dropdown-link-active-bg:#0d6efd;--bs-dropdown-link-disabled-color:#adb5bd;--bs-dropdown-header-color:#adb5bd}.btn-group,.btn-group-vertical{vertical-align:middle;display:inline-flex;position:relative}.btn-group>.btn,.btn-group-vertical>.btn{flex:auto;position:relative}.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn:hover,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn.active{z-index:1}.btn-toolbar{flex-wrap:wrap;justify-content:flex-start;display:flex}.btn-toolbar .input-group{width:auto}.btn-group{border-radius:var(--bs-border-radius)}.btn-group>:not(.btn-check:first-child)+.btn,.btn-group>.btn-group:not(:first-child){margin-left:calc(-1 * var(--bs-border-width))}.btn-group>.btn:not(:last-child):not(.dropdown-toggle),.btn-group>.btn.dropdown-toggle-split:first-child,.btn-group>.btn-group:not(:last-child)>.btn{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn,.btn-group>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-left:.5625rem;padding-right:.5625rem}.dropdown-toggle-split:after,.dropup .dropdown-toggle-split:after,.dropend .dropdown-toggle-split:after{margin-left:0}.dropstart .dropdown-toggle-split:before{margin-right:0}.btn-sm+.dropdown-toggle-split,.btn-group-sm>.btn+.dropdown-toggle-split{padding-left:.375rem;padding-right:.375rem}.btn-lg+.dropdown-toggle-split,.btn-group-lg>.btn+.dropdown-toggle-split{padding-left:.75rem;padding-right:.75rem}.btn-group-vertical{flex-direction:column;justify-content:center;align-items:flex-start}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn:not(:first-child),.btn-group-vertical>.btn-group:not(:first-child){margin-top:calc(-1 * var(--bs-border-width))}.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle),.btn-group-vertical>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:nth-child(n+3),.btn-group-vertical>:not(.btn-check)+.btn,.btn-group-vertical>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{--bs-nav-link-padding-x:1rem;--bs-nav-link-padding-y:.5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color:var(--bs-link-color);--bs-nav-link-hover-color:var(--bs-link-hover-color);--bs-nav-link-disabled-color:var(--bs-secondary-color);flex-wrap:wrap;margin-bottom:0;padding-left:0;list-style:none;display:flex}.nav-link{padding:var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x);font-size:var(--bs-nav-link-font-size);font-weight:var(--bs-nav-link-font-weight);color:var(--bs-nav-link-color);background:0 0;border:0;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out;display:block}@media (prefers-reduced-motion:reduce){.nav-link{transition:none}}.nav-link:hover,.nav-link:focus{color:var(--bs-nav-link-hover-color)}.nav-link:focus-visible{outline:0;box-shadow:0 0 0 .25rem #0d6efd40}.nav-link.disabled,.nav-link:disabled{color:var(--bs-nav-link-disabled-color);pointer-events:none;cursor:default}.nav-tabs{--bs-nav-tabs-border-width:var(--bs-border-width);--bs-nav-tabs-border-color:var(--bs-border-color);--bs-nav-tabs-border-radius:var(--bs-border-radius);--bs-nav-tabs-link-hover-border-color:var(--bs-secondary-bg) var(--bs-secondary-bg) var(--bs-border-color);--bs-nav-tabs-link-active-color:var(--bs-emphasis-color);--bs-nav-tabs-link-active-bg:var(--bs-body-bg);--bs-nav-tabs-link-active-border-color:var(--bs-border-color) var(--bs-border-color) var(--bs-body-bg);border-bottom:var(--bs-nav-tabs-border-width) solid var(--bs-nav-tabs-border-color)}.nav-tabs .nav-link{margin-bottom:calc(-1 * var(--bs-nav-tabs-border-width));border:var(--bs-nav-tabs-border-width) solid transparent;border-top-left-radius:var(--bs-nav-tabs-border-radius);border-top-right-radius:var(--bs-nav-tabs-border-radius)}.nav-tabs .nav-link:hover,.nav-tabs .nav-link:focus{isolation:isolate;border-color:var(--bs-nav-tabs-link-hover-border-color)}.nav-tabs .nav-link.active,.nav-tabs .nav-item.show .nav-link{color:var(--bs-nav-tabs-link-active-color);background-color:var(--bs-nav-tabs-link-active-bg);border-color:var(--bs-nav-tabs-link-active-border-color)}.nav-tabs .dropdown-menu{margin-top:calc(-1 * var(--bs-nav-tabs-border-width));border-top-left-radius:0;border-top-right-radius:0}.nav-pills{--bs-nav-pills-border-radius:var(--bs-border-radius);--bs-nav-pills-link-active-color:#fff;--bs-nav-pills-link-active-bg:#0d6efd}.nav-pills .nav-link{border-radius:var(--bs-nav-pills-border-radius)}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:var(--bs-nav-pills-link-active-color);background-color:var(--bs-nav-pills-link-active-bg)}.nav-underline{--bs-nav-underline-gap:1rem;--bs-nav-underline-border-width:.125rem;--bs-nav-underline-link-active-color:var(--bs-emphasis-color);gap:var(--bs-nav-underline-gap)}.nav-underline .nav-link{border-bottom:var(--bs-nav-underline-border-width) solid transparent;padding-left:0;padding-right:0}.nav-underline .nav-link:hover,.nav-underline .nav-link:focus{border-bottom-color:currentColor}.nav-underline .nav-link.active,.nav-underline .show>.nav-link{color:var(--bs-nav-underline-link-active-color);border-bottom-color:currentColor;font-weight:700}.nav-fill>.nav-link,.nav-fill .nav-item{text-align:center;flex:auto}.nav-justified>.nav-link,.nav-justified .nav-item{text-align:center;flex-grow:1;flex-basis:0}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{--bs-navbar-padding-x:0;--bs-navbar-padding-y:.5rem;--bs-navbar-color:rgba(var(--bs-emphasis-color-rgb), .65);--bs-navbar-hover-color:rgba(var(--bs-emphasis-color-rgb), .8);--bs-navbar-disabled-color:rgba(var(--bs-emphasis-color-rgb), .3);--bs-navbar-active-color:rgba(var(--bs-emphasis-color-rgb), 1);--bs-navbar-brand-padding-y:.3125rem;--bs-navbar-brand-margin-end:1rem;--bs-navbar-brand-font-size:1.25rem;--bs-navbar-brand-color:rgba(var(--bs-emphasis-color-rgb), 1);--bs-navbar-brand-hover-color:rgba(var(--bs-emphasis-color-rgb), 1);--bs-navbar-nav-link-padding-x:.5rem;--bs-navbar-toggler-padding-y:.25rem;--bs-navbar-toggler-padding-x:.75rem;--bs-navbar-toggler-font-size:1.25rem;--bs-navbar-toggler-icon-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%2833, 37, 41, 0.75%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");--bs-navbar-toggler-border-color:rgba(var(--bs-emphasis-color-rgb), .15);--bs-navbar-toggler-border-radius:var(--bs-border-radius);--bs-navbar-toggler-focus-width:.25rem;--bs-navbar-toggler-transition:box-shadow .15s ease-in-out;padding:var(--bs-navbar-padding-y) var(--bs-navbar-padding-x);flex-wrap:wrap;justify-content:space-between;align-items:center;display:flex;position:relative}.navbar>.container,.navbar>.container-fluid,.navbar>.container-sm,.navbar>.container-md,.navbar>.container-lg,.navbar>.container-xl,.navbar>.container-xxl{flex-wrap:inherit;justify-content:space-between;align-items:center;display:flex}.navbar-brand{padding-top:var(--bs-navbar-brand-padding-y);padding-bottom:var(--bs-navbar-brand-padding-y);margin-right:var(--bs-navbar-brand-margin-end);font-size:var(--bs-navbar-brand-font-size);color:var(--bs-navbar-brand-color);white-space:nowrap;text-decoration:none}.navbar-brand:hover,.navbar-brand:focus{color:var(--bs-navbar-brand-hover-color)}.navbar-nav{--bs-nav-link-padding-x:0;--bs-nav-link-padding-y:.5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color:var(--bs-navbar-color);--bs-nav-link-hover-color:var(--bs-navbar-hover-color);--bs-nav-link-disabled-color:var(--bs-navbar-disabled-color);flex-direction:column;margin-bottom:0;padding-left:0;list-style:none;display:flex}.navbar-nav .nav-link.active,.navbar-nav .nav-link.show{color:var(--bs-navbar-active-color)}.navbar-nav .dropdown-menu{position:static}.navbar-text{color:var(--bs-navbar-color);padding-top:.5rem;padding-bottom:.5rem}.navbar-text a,.navbar-text a:hover,.navbar-text a:focus{color:var(--bs-navbar-active-color)}.navbar-collapse{flex-grow:1;flex-basis:100%;align-items:center}.navbar-toggler{padding:var(--bs-navbar-toggler-padding-y) var(--bs-navbar-toggler-padding-x);font-size:var(--bs-navbar-toggler-font-size);color:var(--bs-navbar-color);border:var(--bs-border-width) solid var(--bs-navbar-toggler-border-color);border-radius:var(--bs-navbar-toggler-border-radius);transition:var(--bs-navbar-toggler-transition);background-color:#0000;line-height:1}@media (prefers-reduced-motion:reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{box-shadow:0 0 0 var(--bs-navbar-toggler-focus-width);outline:0;text-decoration:none}.navbar-toggler-icon{vertical-align:middle;background-image:var(--bs-navbar-toggler-icon-bg);background-position:50%;background-repeat:no-repeat;background-size:100%;width:1.5em;height:1.5em;display:inline-block}.navbar-nav-scroll{max-height:var(--bs-scroll-height,75vh);overflow-y:auto}@media (width>=576px){.navbar-expand-sm{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{flex-basis:auto;display:flex!important}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas{z-index:auto;flex-grow:1;transition:none;position:static;visibility:visible!important;background-color:#0000!important;border:0!important;width:auto!important;height:auto!important;transform:none!important}.navbar-expand-sm .offcanvas .offcanvas-header{display:none}.navbar-expand-sm .offcanvas .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible}}@media (width>=768px){.navbar-expand-md{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{flex-basis:auto;display:flex!important}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas{z-index:auto;flex-grow:1;transition:none;position:static;visibility:visible!important;background-color:#0000!important;border:0!important;width:auto!important;height:auto!important;transform:none!important}.navbar-expand-md .offcanvas .offcanvas-header{display:none}.navbar-expand-md .offcanvas .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible}}@media (width>=992px){.navbar-expand-lg{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{flex-basis:auto;display:flex!important}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas{z-index:auto;flex-grow:1;transition:none;position:static;visibility:visible!important;background-color:#0000!important;border:0!important;width:auto!important;height:auto!important;transform:none!important}.navbar-expand-lg .offcanvas .offcanvas-header{display:none}.navbar-expand-lg .offcanvas .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible}}@media (width>=1200px){.navbar-expand-xl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{flex-basis:auto;display:flex!important}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas{z-index:auto;flex-grow:1;transition:none;position:static;visibility:visible!important;background-color:#0000!important;border:0!important;width:auto!important;height:auto!important;transform:none!important}.navbar-expand-xl .offcanvas .offcanvas-header{display:none}.navbar-expand-xl .offcanvas .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible}}@media (width>=1400px){.navbar-expand-xxl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{flex-basis:auto;display:flex!important}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas{z-index:auto;flex-grow:1;transition:none;position:static;visibility:visible!important;background-color:#0000!important;border:0!important;width:auto!important;height:auto!important;transform:none!important}.navbar-expand-xxl .offcanvas .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible}}.navbar-expand{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{flex-basis:auto;display:flex!important}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas{z-index:auto;flex-grow:1;transition:none;position:static;visibility:visible!important;background-color:#0000!important;border:0!important;width:auto!important;height:auto!important;transform:none!important}.navbar-expand .offcanvas .offcanvas-header{display:none}.navbar-expand .offcanvas .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible}.navbar-dark,.navbar[data-bs-theme=dark]{--bs-navbar-color:#ffffff8c;--bs-navbar-hover-color:#ffffffbf;--bs-navbar-disabled-color:#ffffff40;--bs-navbar-active-color:#fff;--bs-navbar-brand-color:#fff;--bs-navbar-brand-hover-color:#fff;--bs-navbar-toggler-border-color:#ffffff1a;--bs-navbar-toggler-icon-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}[data-bs-theme=dark] .navbar-toggler-icon{--bs-navbar-toggler-icon-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.card{--bs-card-spacer-y:1rem;--bs-card-spacer-x:1rem;--bs-card-title-spacer-y:.5rem;--bs-card-title-color: ;--bs-card-subtitle-color: ;--bs-card-border-width:var(--bs-border-width);--bs-card-border-color:var(--bs-border-color-translucent);--bs-card-border-radius:var(--bs-border-radius);--bs-card-box-shadow: ;--bs-card-inner-border-radius:calc(var(--bs-border-radius) - (var(--bs-border-width)));--bs-card-cap-padding-y:.5rem;--bs-card-cap-padding-x:1rem;--bs-card-cap-bg:rgba(var(--bs-body-color-rgb), .03);--bs-card-cap-color: ;--bs-card-height: ;--bs-card-color: ;--bs-card-bg:var(--bs-body-bg);--bs-card-img-overlay-padding:1rem;--bs-card-group-margin:.75rem;min-width:0;height:var(--bs-card-height);color:var(--bs-body-color);word-wrap:break-word;background-color:var(--bs-card-bg);border:var(--bs-card-border-width) solid var(--bs-card-border-color);border-radius:var(--bs-card-border-radius);background-clip:border-box;flex-direction:column;display:flex;position:relative}.card>hr{margin-left:0;margin-right:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-left-radius:var(--bs-card-inner-border-radius);border-top-right-radius:var(--bs-card-inner-border-radius);border-top-width:0}.card>.list-group:last-child{border-bottom-right-radius:var(--bs-card-inner-border-radius);border-bottom-left-radius:var(--bs-card-inner-border-radius);border-bottom-width:0}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{padding:var(--bs-card-spacer-y) var(--bs-card-spacer-x);color:var(--bs-card-color);flex:auto}.card-title{margin-bottom:var(--bs-card-title-spacer-y);color:var(--bs-card-title-color)}.card-subtitle{margin-top:calc(-.5 * var(--bs-card-title-spacer-y));color:var(--bs-card-subtitle-color);margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:var(--bs-card-spacer-x)}.card-header{padding:var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);color:var(--bs-card-cap-color);background-color:var(--bs-card-cap-bg);border-bottom:var(--bs-card-border-width) solid var(--bs-card-border-color);margin-bottom:0}.card-header:first-child{border-radius:var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius) 0 0}.card-footer{padding:var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);color:var(--bs-card-cap-color);background-color:var(--bs-card-cap-bg);border-top:var(--bs-card-border-width) solid var(--bs-card-border-color)}.card-footer:last-child{border-radius:0 0 var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius)}.card-header-tabs{margin-right:calc(-.5 * var(--bs-card-cap-padding-x));margin-bottom:calc(-1 * var(--bs-card-cap-padding-y));margin-left:calc(-.5 * var(--bs-card-cap-padding-x));border-bottom:0}.card-header-tabs .nav-link.active{background-color:var(--bs-card-bg);border-bottom-color:var(--bs-card-bg)}.card-header-pills{margin-right:calc(-.5 * var(--bs-card-cap-padding-x));margin-left:calc(-.5 * var(--bs-card-cap-padding-x))}.card-img-overlay{padding:var(--bs-card-img-overlay-padding);border-radius:var(--bs-card-inner-border-radius);position:absolute;inset:0}.card-img,.card-img-top,.card-img-bottom{width:100%}.card-img,.card-img-top{border-top-left-radius:var(--bs-card-inner-border-radius);border-top-right-radius:var(--bs-card-inner-border-radius)}.card-img,.card-img-bottom{border-bottom-right-radius:var(--bs-card-inner-border-radius);border-bottom-left-radius:var(--bs-card-inner-border-radius)}.card-group>.card{margin-bottom:var(--bs-card-group-margin)}@media (width>=576px){.card-group{flex-flow:wrap;display:flex}.card-group>.card{flex:1 0 0;margin-bottom:0}.card-group>.card+.card{border-left:0;margin-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child)>.card-img-top,.card-group>.card:not(:last-child)>.card-header{border-top-right-radius:0}.card-group>.card:not(:last-child)>.card-img-bottom,.card-group>.card:not(:last-child)>.card-footer{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child)>.card-img-top,.card-group>.card:not(:first-child)>.card-header{border-top-left-radius:0}.card-group>.card:not(:first-child)>.card-img-bottom,.card-group>.card:not(:first-child)>.card-footer{border-bottom-left-radius:0}}.accordion{--bs-accordion-color:var(--bs-body-color);--bs-accordion-bg:var(--bs-body-bg);--bs-accordion-transition:color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out, border-radius .15s ease;--bs-accordion-border-color:var(--bs-border-color);--bs-accordion-border-width:var(--bs-border-width);--bs-accordion-border-radius:var(--bs-border-radius);--bs-accordion-inner-border-radius:calc(var(--bs-border-radius) - (var(--bs-border-width)));--bs-accordion-btn-padding-x:1.25rem;--bs-accordion-btn-padding-y:1rem;--bs-accordion-btn-color:var(--bs-body-color);--bs-accordion-btn-bg:var(--bs-accordion-bg);--bs-accordion-btn-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23212529' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='m2 5 6 6 6-6'/%3e%3c/svg%3e");--bs-accordion-btn-icon-width:1.25rem;--bs-accordion-btn-icon-transform:rotate(-180deg);--bs-accordion-btn-icon-transition:transform .2s ease-in-out;--bs-accordion-btn-active-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='rgb%282.0392156863%, 17.2549019608%, 39.6862745098%%29' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='m2 5 6 6 6-6'/%3e%3c/svg%3e");--bs-accordion-btn-focus-box-shadow:0 0 0 .25rem #0d6efd40;--bs-accordion-body-padding-x:1.25rem;--bs-accordion-body-padding-y:1rem;--bs-accordion-active-color:var(--bs-primary-text-emphasis);--bs-accordion-active-bg:var(--bs-primary-bg-subtle)}.accordion-button{width:100%;padding:var(--bs-accordion-btn-padding-y) var(--bs-accordion-btn-padding-x);color:var(--bs-accordion-btn-color);text-align:left;background-color:var(--bs-accordion-btn-bg);overflow-anchor:none;transition:var(--bs-accordion-transition);border:0;border-radius:0;align-items:center;font-size:1rem;display:flex;position:relative}@media (prefers-reduced-motion:reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:var(--bs-accordion-active-color);background-color:var(--bs-accordion-active-bg);box-shadow:inset 0 calc(-1 * var(--bs-accordion-border-width)) 0 var(--bs-accordion-border-color)}.accordion-button:not(.collapsed):after{background-image:var(--bs-accordion-btn-active-icon);transform:var(--bs-accordion-btn-icon-transform)}.accordion-button:after{width:var(--bs-accordion-btn-icon-width);height:var(--bs-accordion-btn-icon-width);content:"";background-image:var(--bs-accordion-btn-icon);background-repeat:no-repeat;background-size:var(--bs-accordion-btn-icon-width);transition:var(--bs-accordion-btn-icon-transition);flex-shrink:0;margin-left:auto}@media (prefers-reduced-motion:reduce){.accordion-button:after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;box-shadow:var(--bs-accordion-btn-focus-box-shadow);outline:0}.accordion-header{margin-bottom:0}.accordion-item{color:var(--bs-accordion-color);background-color:var(--bs-accordion-bg);border:var(--bs-accordion-border-width) solid var(--bs-accordion-border-color)}.accordion-item:first-of-type{border-top-left-radius:var(--bs-accordion-border-radius);border-top-right-radius:var(--bs-accordion-border-radius)}.accordion-item:first-of-type>.accordion-header .accordion-button{border-top-left-radius:var(--bs-accordion-inner-border-radius);border-top-right-radius:var(--bs-accordion-inner-border-radius)}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-right-radius:var(--bs-accordion-border-radius);border-bottom-left-radius:var(--bs-accordion-border-radius)}.accordion-item:last-of-type>.accordion-header .accordion-button.collapsed{border-bottom-right-radius:var(--bs-accordion-inner-border-radius);border-bottom-left-radius:var(--bs-accordion-inner-border-radius)}.accordion-item:last-of-type>.accordion-collapse{border-bottom-right-radius:var(--bs-accordion-border-radius);border-bottom-left-radius:var(--bs-accordion-border-radius)}.accordion-body{padding:var(--bs-accordion-body-padding-y) var(--bs-accordion-body-padding-x)}.accordion-flush>.accordion-item{border-left:0;border-right:0;border-radius:0}.accordion-flush>.accordion-item:first-child{border-top:0}.accordion-flush>.accordion-item:last-child{border-bottom:0}.accordion-flush>.accordion-item>.accordion-collapse,.accordion-flush>.accordion-item>.accordion-header .accordion-button,.accordion-flush>.accordion-item>.accordion-header .accordion-button.collapsed{border-radius:0}[data-bs-theme=dark] .accordion-button:after{--bs-accordion-btn-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='rgb%2843.0588235294%, 65.8823529412%, 99.5294117647%%29'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708'/%3e%3c/svg%3e");--bs-accordion-btn-active-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='rgb%2843.0588235294%, 65.8823529412%, 99.5294117647%%29'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708'/%3e%3c/svg%3e")}.breadcrumb{--bs-breadcrumb-padding-x:0;--bs-breadcrumb-padding-y:0;--bs-breadcrumb-margin-bottom:1rem;--bs-breadcrumb-bg: ;--bs-breadcrumb-border-radius: ;--bs-breadcrumb-divider-color:var(--bs-secondary-color);--bs-breadcrumb-item-padding-x:.5rem;--bs-breadcrumb-item-active-color:var(--bs-secondary-color);padding:var(--bs-breadcrumb-padding-y) var(--bs-breadcrumb-padding-x);margin-bottom:var(--bs-breadcrumb-margin-bottom);font-size:var(--bs-breadcrumb-font-size);background-color:var(--bs-breadcrumb-bg);border-radius:var(--bs-breadcrumb-border-radius);flex-wrap:wrap;list-style:none;display:flex}.breadcrumb-item+.breadcrumb-item{padding-left:var(--bs-breadcrumb-item-padding-x)}.breadcrumb-item+.breadcrumb-item:before{float:left;padding-right:var(--bs-breadcrumb-item-padding-x);color:var(--bs-breadcrumb-divider-color);content:var(--bs-breadcrumb-divider,"/") }.breadcrumb-item.active{color:var(--bs-breadcrumb-item-active-color)}.pagination{--bs-pagination-padding-x:.75rem;--bs-pagination-padding-y:.375rem;--bs-pagination-font-size:1rem;--bs-pagination-color:var(--bs-link-color);--bs-pagination-bg:var(--bs-body-bg);--bs-pagination-border-width:var(--bs-border-width);--bs-pagination-border-color:var(--bs-border-color);--bs-pagination-border-radius:var(--bs-border-radius);--bs-pagination-hover-color:var(--bs-link-hover-color);--bs-pagination-hover-bg:var(--bs-tertiary-bg);--bs-pagination-hover-border-color:var(--bs-border-color);--bs-pagination-focus-color:var(--bs-link-hover-color);--bs-pagination-focus-bg:var(--bs-secondary-bg);--bs-pagination-focus-box-shadow:0 0 0 .25rem #0d6efd40;--bs-pagination-active-color:#fff;--bs-pagination-active-bg:#0d6efd;--bs-pagination-active-border-color:#0d6efd;--bs-pagination-disabled-color:var(--bs-secondary-color);--bs-pagination-disabled-bg:var(--bs-secondary-bg);--bs-pagination-disabled-border-color:var(--bs-border-color);padding-left:0;list-style:none;display:flex}.page-link{padding:var(--bs-pagination-padding-y) var(--bs-pagination-padding-x);font-size:var(--bs-pagination-font-size);color:var(--bs-pagination-color);background-color:var(--bs-pagination-bg);border:var(--bs-pagination-border-width) solid var(--bs-pagination-border-color);text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;display:block;position:relative}@media (prefers-reduced-motion:reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:var(--bs-pagination-hover-color);background-color:var(--bs-pagination-hover-bg);border-color:var(--bs-pagination-hover-border-color)}.page-link:focus{z-index:3;color:var(--bs-pagination-focus-color);background-color:var(--bs-pagination-focus-bg);box-shadow:var(--bs-pagination-focus-box-shadow);outline:0}.page-link.active,.active>.page-link{z-index:3;color:var(--bs-pagination-active-color);background-color:var(--bs-pagination-active-bg);border-color:var(--bs-pagination-active-border-color)}.page-link.disabled,.disabled>.page-link{color:var(--bs-pagination-disabled-color);pointer-events:none;background-color:var(--bs-pagination-disabled-bg);border-color:var(--bs-pagination-disabled-border-color)}.page-item:not(:first-child) .page-link{margin-left:calc(-1 * var(--bs-border-width))}.page-item:first-child .page-link{border-top-left-radius:var(--bs-pagination-border-radius);border-bottom-left-radius:var(--bs-pagination-border-radius)}.page-item:last-child .page-link{border-top-right-radius:var(--bs-pagination-border-radius);border-bottom-right-radius:var(--bs-pagination-border-radius)}.pagination-lg{--bs-pagination-padding-x:1.5rem;--bs-pagination-padding-y:.75rem;--bs-pagination-font-size:1.25rem;--bs-pagination-border-radius:var(--bs-border-radius-lg)}.pagination-sm{--bs-pagination-padding-x:.5rem;--bs-pagination-padding-y:.25rem;--bs-pagination-font-size:.875rem;--bs-pagination-border-radius:var(--bs-border-radius-sm)}.badge{--bs-badge-padding-x:.65em;--bs-badge-padding-y:.35em;--bs-badge-font-size:.75em;--bs-badge-font-weight:700;--bs-badge-color:#fff;--bs-badge-border-radius:var(--bs-border-radius);padding:var(--bs-badge-padding-y) var(--bs-badge-padding-x);font-size:var(--bs-badge-font-size);font-weight:var(--bs-badge-font-weight);color:var(--bs-badge-color);text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:var(--bs-badge-border-radius);line-height:1;display:inline-block}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{--bs-alert-bg:transparent;--bs-alert-padding-x:1rem;--bs-alert-padding-y:1rem;--bs-alert-margin-bottom:1rem;--bs-alert-color:inherit;--bs-alert-border-color:transparent;--bs-alert-border:var(--bs-border-width) solid var(--bs-alert-border-color);--bs-alert-border-radius:var(--bs-border-radius);--bs-alert-link-color:inherit;padding:var(--bs-alert-padding-y) var(--bs-alert-padding-x);margin-bottom:var(--bs-alert-margin-bottom);color:var(--bs-alert-color);background-color:var(--bs-alert-bg);border:var(--bs-alert-border);border-radius:var(--bs-alert-border-radius);position:relative}.alert-heading{color:inherit}.alert-link{color:var(--bs-alert-link-color);font-weight:700}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{z-index:2;padding:1.25rem 1rem;position:absolute;top:0;right:0}.alert-primary{--bs-alert-color:var(--bs-primary-text-emphasis);--bs-alert-bg:var(--bs-primary-bg-subtle);--bs-alert-border-color:var(--bs-primary-border-subtle);--bs-alert-link-color:var(--bs-primary-text-emphasis)}.alert-secondary{--bs-alert-color:var(--bs-secondary-text-emphasis);--bs-alert-bg:var(--bs-secondary-bg-subtle);--bs-alert-border-color:var(--bs-secondary-border-subtle);--bs-alert-link-color:var(--bs-secondary-text-emphasis)}.alert-success{--bs-alert-color:var(--bs-success-text-emphasis);--bs-alert-bg:var(--bs-success-bg-subtle);--bs-alert-border-color:var(--bs-success-border-subtle);--bs-alert-link-color:var(--bs-success-text-emphasis)}.alert-info{--bs-alert-color:var(--bs-info-text-emphasis);--bs-alert-bg:var(--bs-info-bg-subtle);--bs-alert-border-color:var(--bs-info-border-subtle);--bs-alert-link-color:var(--bs-info-text-emphasis)}.alert-warning{--bs-alert-color:var(--bs-warning-text-emphasis);--bs-alert-bg:var(--bs-warning-bg-subtle);--bs-alert-border-color:var(--bs-warning-border-subtle);--bs-alert-link-color:var(--bs-warning-text-emphasis)}.alert-danger{--bs-alert-color:var(--bs-danger-text-emphasis);--bs-alert-bg:var(--bs-danger-bg-subtle);--bs-alert-border-color:var(--bs-danger-border-subtle);--bs-alert-link-color:var(--bs-danger-text-emphasis)}.alert-light{--bs-alert-color:var(--bs-light-text-emphasis);--bs-alert-bg:var(--bs-light-bg-subtle);--bs-alert-border-color:var(--bs-light-border-subtle);--bs-alert-link-color:var(--bs-light-text-emphasis)}.alert-dark{--bs-alert-color:var(--bs-dark-text-emphasis);--bs-alert-bg:var(--bs-dark-bg-subtle);--bs-alert-border-color:var(--bs-dark-border-subtle);--bs-alert-link-color:var(--bs-dark-text-emphasis)}@keyframes progress-bar-stripes{0%{background-position-x:var(--bs-progress-height)}}.progress,.progress-stacked{--bs-progress-height:1rem;--bs-progress-font-size:.75rem;--bs-progress-bg:var(--bs-secondary-bg);--bs-progress-border-radius:var(--bs-border-radius);--bs-progress-box-shadow:var(--bs-box-shadow-inset);--bs-progress-bar-color:#fff;--bs-progress-bar-bg:#0d6efd;--bs-progress-bar-transition:width .6s ease;height:var(--bs-progress-height);font-size:var(--bs-progress-font-size);background-color:var(--bs-progress-bg);border-radius:var(--bs-progress-border-radius);display:flex;overflow:hidden}.progress-bar{color:var(--bs-progress-bar-color);text-align:center;white-space:nowrap;background-color:var(--bs-progress-bar-bg);transition:var(--bs-progress-bar-transition);flex-direction:column;justify-content:center;display:flex;overflow:hidden}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,#ffffff26 25%,#0000 25% 50%,#ffffff26 50% 75%,#0000 75%,#0000);background-size:var(--bs-progress-height) var(--bs-progress-height)}.progress-stacked>.progress{overflow:visible}.progress-stacked>.progress>.progress-bar{width:100%}.progress-bar-animated{animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion:reduce){.progress-bar-animated{animation:none}}.list-group{--bs-list-group-color:var(--bs-body-color);--bs-list-group-bg:var(--bs-body-bg);--bs-list-group-border-color:var(--bs-border-color);--bs-list-group-border-width:var(--bs-border-width);--bs-list-group-border-radius:var(--bs-border-radius);--bs-list-group-item-padding-x:1rem;--bs-list-group-item-padding-y:.5rem;--bs-list-group-action-color:var(--bs-secondary-color);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-tertiary-bg);--bs-list-group-action-active-color:var(--bs-body-color);--bs-list-group-action-active-bg:var(--bs-secondary-bg);--bs-list-group-disabled-color:var(--bs-secondary-color);--bs-list-group-disabled-bg:var(--bs-body-bg);--bs-list-group-active-color:#fff;--bs-list-group-active-bg:#0d6efd;--bs-list-group-active-border-color:#0d6efd;border-radius:var(--bs-list-group-border-radius);flex-direction:column;margin-bottom:0;padding-left:0;display:flex}.list-group-numbered{counter-reset:section;list-style-type:none}.list-group-numbered>.list-group-item:before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item{padding:var(--bs-list-group-item-padding-y) var(--bs-list-group-item-padding-x);color:var(--bs-list-group-color);background-color:var(--bs-list-group-bg);border:var(--bs-list-group-border-width) solid var(--bs-list-group-border-color);text-decoration:none;display:block;position:relative}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:var(--bs-list-group-disabled-color);pointer-events:none;background-color:var(--bs-list-group-disabled-bg)}.list-group-item.active{z-index:2;color:var(--bs-list-group-active-color);background-color:var(--bs-list-group-active-bg);border-color:var(--bs-list-group-active-border-color)}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:calc(-1 * var(--bs-list-group-border-width));border-top-width:var(--bs-list-group-border-width)}.list-group-item-action{width:100%;color:var(--bs-list-group-action-color);text-align:inherit}.list-group-item-action:not(.active):hover,.list-group-item-action:not(.active):focus{z-index:1;color:var(--bs-list-group-action-hover-color);background-color:var(--bs-list-group-action-hover-bg);text-decoration:none}.list-group-item-action:not(.active):active{color:var(--bs-list-group-action-active-color);background-color:var(--bs-list-group-action-active-bg)}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}@media (width>=576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (width>=768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (width>=992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (width>=1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (width>=1400px){.list-group-horizontal-xxl{flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 var(--bs-list-group-border-width)}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{--bs-list-group-color:var(--bs-primary-text-emphasis);--bs-list-group-bg:var(--bs-primary-bg-subtle);--bs-list-group-border-color:var(--bs-primary-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-primary-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-primary-border-subtle);--bs-list-group-active-color:var(--bs-primary-bg-subtle);--bs-list-group-active-bg:var(--bs-primary-text-emphasis);--bs-list-group-active-border-color:var(--bs-primary-text-emphasis)}.list-group-item-secondary{--bs-list-group-color:var(--bs-secondary-text-emphasis);--bs-list-group-bg:var(--bs-secondary-bg-subtle);--bs-list-group-border-color:var(--bs-secondary-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-secondary-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-secondary-border-subtle);--bs-list-group-active-color:var(--bs-secondary-bg-subtle);--bs-list-group-active-bg:var(--bs-secondary-text-emphasis);--bs-list-group-active-border-color:var(--bs-secondary-text-emphasis)}.list-group-item-success{--bs-list-group-color:var(--bs-success-text-emphasis);--bs-list-group-bg:var(--bs-success-bg-subtle);--bs-list-group-border-color:var(--bs-success-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-success-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-success-border-subtle);--bs-list-group-active-color:var(--bs-success-bg-subtle);--bs-list-group-active-bg:var(--bs-success-text-emphasis);--bs-list-group-active-border-color:var(--bs-success-text-emphasis)}.list-group-item-info{--bs-list-group-color:var(--bs-info-text-emphasis);--bs-list-group-bg:var(--bs-info-bg-subtle);--bs-list-group-border-color:var(--bs-info-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-info-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-info-border-subtle);--bs-list-group-active-color:var(--bs-info-bg-subtle);--bs-list-group-active-bg:var(--bs-info-text-emphasis);--bs-list-group-active-border-color:var(--bs-info-text-emphasis)}.list-group-item-warning{--bs-list-group-color:var(--bs-warning-text-emphasis);--bs-list-group-bg:var(--bs-warning-bg-subtle);--bs-list-group-border-color:var(--bs-warning-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-warning-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-warning-border-subtle);--bs-list-group-active-color:var(--bs-warning-bg-subtle);--bs-list-group-active-bg:var(--bs-warning-text-emphasis);--bs-list-group-active-border-color:var(--bs-warning-text-emphasis)}.list-group-item-danger{--bs-list-group-color:var(--bs-danger-text-emphasis);--bs-list-group-bg:var(--bs-danger-bg-subtle);--bs-list-group-border-color:var(--bs-danger-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-danger-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-danger-border-subtle);--bs-list-group-active-color:var(--bs-danger-bg-subtle);--bs-list-group-active-bg:var(--bs-danger-text-emphasis);--bs-list-group-active-border-color:var(--bs-danger-text-emphasis)}.list-group-item-light{--bs-list-group-color:var(--bs-light-text-emphasis);--bs-list-group-bg:var(--bs-light-bg-subtle);--bs-list-group-border-color:var(--bs-light-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-light-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-light-border-subtle);--bs-list-group-active-color:var(--bs-light-bg-subtle);--bs-list-group-active-bg:var(--bs-light-text-emphasis);--bs-list-group-active-border-color:var(--bs-light-text-emphasis)}.list-group-item-dark{--bs-list-group-color:var(--bs-dark-text-emphasis);--bs-list-group-bg:var(--bs-dark-bg-subtle);--bs-list-group-border-color:var(--bs-dark-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-dark-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-dark-border-subtle);--bs-list-group-active-color:var(--bs-dark-bg-subtle);--bs-list-group-active-bg:var(--bs-dark-text-emphasis);--bs-list-group-active-border-color:var(--bs-dark-text-emphasis)}.btn-close{--bs-btn-close-color:#000;--bs-btn-close-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414'/%3e%3c/svg%3e");--bs-btn-close-opacity:.5;--bs-btn-close-hover-opacity:.75;--bs-btn-close-focus-shadow:0 0 0 .25rem #0d6efd40;--bs-btn-close-focus-opacity:1;--bs-btn-close-disabled-opacity:.25;box-sizing:content-box;width:1em;height:1em;color:var(--bs-btn-close-color);background:transparent var(--bs-btn-close-bg) center/1em auto no-repeat;filter:var(--bs-btn-close-filter);opacity:var(--bs-btn-close-opacity);border:0;border-radius:.375rem;padding:.25em}.btn-close:hover{color:var(--bs-btn-close-color);opacity:var(--bs-btn-close-hover-opacity);text-decoration:none}.btn-close:focus{box-shadow:var(--bs-btn-close-focus-shadow);opacity:var(--bs-btn-close-focus-opacity);outline:0}.btn-close:disabled,.btn-close.disabled{pointer-events:none;-webkit-user-select:none;user-select:none;opacity:var(--bs-btn-close-disabled-opacity)}.btn-close-white{--bs-btn-close-filter:invert(1) grayscale(100%) brightness(200%)}:root,[data-bs-theme=light]{--bs-btn-close-filter: }[data-bs-theme=dark]{--bs-btn-close-filter:invert(1) grayscale(100%) brightness(200%)}.toast{--bs-toast-zindex:1090;--bs-toast-padding-x:.75rem;--bs-toast-padding-y:.5rem;--bs-toast-spacing:1.5rem;--bs-toast-max-width:350px;--bs-toast-font-size:.875rem;--bs-toast-color: ;--bs-toast-bg:rgba(var(--bs-body-bg-rgb), .85);--bs-toast-border-width:var(--bs-border-width);--bs-toast-border-color:var(--bs-border-color-translucent);--bs-toast-border-radius:var(--bs-border-radius);--bs-toast-box-shadow:var(--bs-box-shadow);--bs-toast-header-color:var(--bs-secondary-color);--bs-toast-header-bg:rgba(var(--bs-body-bg-rgb), .85);--bs-toast-header-border-color:var(--bs-border-color-translucent);width:var(--bs-toast-max-width);max-width:100%;font-size:var(--bs-toast-font-size);color:var(--bs-toast-color);pointer-events:auto;background-color:var(--bs-toast-bg);border:var(--bs-toast-border-width) solid var(--bs-toast-border-color);box-shadow:var(--bs-toast-box-shadow);border-radius:var(--bs-toast-border-radius);background-clip:padding-box}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{--bs-toast-zindex:1090;z-index:var(--bs-toast-zindex);pointer-events:none;width:max-content;max-width:100%;position:absolute}.toast-container>:not(:last-child){margin-bottom:var(--bs-toast-spacing)}.toast-header{padding:var(--bs-toast-padding-y) var(--bs-toast-padding-x);color:var(--bs-toast-header-color);background-color:var(--bs-toast-header-bg);border-bottom:var(--bs-toast-border-width) solid var(--bs-toast-header-border-color);border-top-left-radius:calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width));border-top-right-radius:calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width));background-clip:padding-box;align-items:center;display:flex}.toast-header .btn-close{margin-right:calc(-.5 * var(--bs-toast-padding-x));margin-left:var(--bs-toast-padding-x)}.toast-body{padding:var(--bs-toast-padding-x);word-wrap:break-word}.modal{--bs-modal-zindex:1055;--bs-modal-width:500px;--bs-modal-padding:1rem;--bs-modal-margin:.5rem;--bs-modal-color:var(--bs-body-color);--bs-modal-bg:var(--bs-body-bg);--bs-modal-border-color:var(--bs-border-color-translucent);--bs-modal-border-width:var(--bs-border-width);--bs-modal-border-radius:var(--bs-border-radius-lg);--bs-modal-box-shadow:var(--bs-box-shadow-sm);--bs-modal-inner-border-radius:calc(var(--bs-border-radius-lg) - (var(--bs-border-width)));--bs-modal-header-padding-x:1rem;--bs-modal-header-padding-y:1rem;--bs-modal-header-padding:1rem 1rem;--bs-modal-header-border-color:var(--bs-border-color);--bs-modal-header-border-width:var(--bs-border-width);--bs-modal-title-line-height:1.5;--bs-modal-footer-gap:.5rem;--bs-modal-footer-bg: ;--bs-modal-footer-border-color:var(--bs-border-color);--bs-modal-footer-border-width:var(--bs-border-width);z-index:var(--bs-modal-zindex);outline:0;width:100%;height:100%;display:none;position:fixed;top:0;left:0;overflow:hidden auto}.modal-dialog{width:auto;margin:var(--bs-modal-margin);pointer-events:none;position:relative}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translateY(-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - var(--bs-modal-margin) * 2)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{min-height:calc(100% - var(--bs-modal-margin) * 2);align-items:center;display:flex}.modal-content{width:100%;color:var(--bs-modal-color);pointer-events:auto;background-color:var(--bs-modal-bg);border:var(--bs-modal-border-width) solid var(--bs-modal-border-color);border-radius:var(--bs-modal-border-radius);background-clip:padding-box;outline:0;flex-direction:column;display:flex;position:relative}.modal-backdrop{--bs-backdrop-zindex:1050;--bs-backdrop-bg:#000;--bs-backdrop-opacity:.5;z-index:var(--bs-backdrop-zindex);background-color:var(--bs-backdrop-bg);width:100vw;height:100vh;position:fixed;top:0;left:0}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:var(--bs-backdrop-opacity)}.modal-header{padding:var(--bs-modal-header-padding);border-bottom:var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color);border-top-left-radius:var(--bs-modal-inner-border-radius);border-top-right-radius:var(--bs-modal-inner-border-radius);flex-shrink:0;align-items:center;display:flex}.modal-header .btn-close{padding:calc(var(--bs-modal-header-padding-y) * .5) calc(var(--bs-modal-header-padding-x) * .5);margin-top:calc(-.5 * var(--bs-modal-header-padding-y));margin-right:calc(-.5 * var(--bs-modal-header-padding-x));margin-bottom:calc(-.5 * var(--bs-modal-header-padding-y));margin-left:auto}.modal-title{line-height:var(--bs-modal-title-line-height);margin-bottom:0}.modal-body{padding:var(--bs-modal-padding);flex:auto;position:relative}.modal-footer{padding:calc(var(--bs-modal-padding) - var(--bs-modal-footer-gap) * .5);background-color:var(--bs-modal-footer-bg);border-top:var(--bs-modal-footer-border-width) solid var(--bs-modal-footer-border-color);border-bottom-right-radius:var(--bs-modal-inner-border-radius);border-bottom-left-radius:var(--bs-modal-inner-border-radius);flex-wrap:wrap;flex-shrink:0;justify-content:flex-end;align-items:center;display:flex}.modal-footer>*{margin:calc(var(--bs-modal-footer-gap) * .5)}@media (width>=576px){.modal{--bs-modal-margin:1.75rem;--bs-modal-box-shadow:var(--bs-box-shadow)}.modal-dialog{max-width:var(--bs-modal-width);margin-left:auto;margin-right:auto}.modal-sm{--bs-modal-width:300px}}@media (width>=992px){.modal-lg,.modal-xl{--bs-modal-width:800px}}@media (width>=1200px){.modal-xl{--bs-modal-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{border:0;border-radius:0;height:100%}.modal-fullscreen .modal-header,.modal-fullscreen .modal-footer{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}@media (width<=575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{border:0;border-radius:0;height:100%}.modal-fullscreen-sm-down .modal-header,.modal-fullscreen-sm-down .modal-footer{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}}@media (width<=767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{border:0;border-radius:0;height:100%}.modal-fullscreen-md-down .modal-header,.modal-fullscreen-md-down .modal-footer{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}}@media (width<=991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{border:0;border-radius:0;height:100%}.modal-fullscreen-lg-down .modal-header,.modal-fullscreen-lg-down .modal-footer{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}}@media (width<=1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{border:0;border-radius:0;height:100%}.modal-fullscreen-xl-down .modal-header,.modal-fullscreen-xl-down .modal-footer{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}}@media (width<=1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{border:0;border-radius:0;height:100%}.modal-fullscreen-xxl-down .modal-header,.modal-fullscreen-xxl-down .modal-footer{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}}.tooltip{--bs-tooltip-zindex:1080;--bs-tooltip-max-width:200px;--bs-tooltip-padding-x:.5rem;--bs-tooltip-padding-y:.25rem;--bs-tooltip-margin: ;--bs-tooltip-font-size:.875rem;--bs-tooltip-color:var(--bs-body-bg);--bs-tooltip-bg:var(--bs-emphasis-color);--bs-tooltip-border-radius:var(--bs-border-radius);--bs-tooltip-opacity:.9;--bs-tooltip-arrow-width:.8rem;--bs-tooltip-arrow-height:.4rem;z-index:var(--bs-tooltip-zindex);margin:var(--bs-tooltip-margin);font-family:var(--bs-font-sans-serif);text-align:left;text-align:start;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;white-space:normal;word-spacing:normal;line-break:auto;font-style:normal;font-weight:400;line-height:1.5;font-size:var(--bs-tooltip-font-size);word-wrap:break-word;opacity:0;text-decoration:none;display:block}.tooltip.show{opacity:var(--bs-tooltip-opacity)}.tooltip .tooltip-arrow{width:var(--bs-tooltip-arrow-width);height:var(--bs-tooltip-arrow-height);display:block}.tooltip .tooltip-arrow:before{content:"";border-style:solid;border-color:#0000;position:absolute}.bs-tooltip-top .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow{bottom:calc(-1 * var(--bs-tooltip-arrow-height))}.bs-tooltip-top .tooltip-arrow:before,.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow:before{border-width:var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * .5) 0;border-top-color:var(--bs-tooltip-bg);top:-1px}.bs-tooltip-end .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow{left:calc(-1 * var(--bs-tooltip-arrow-height));width:var(--bs-tooltip-arrow-height);height:var(--bs-tooltip-arrow-width)}.bs-tooltip-end .tooltip-arrow:before,.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow:before{border-width:calc(var(--bs-tooltip-arrow-width) * .5) var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * .5) 0;border-right-color:var(--bs-tooltip-bg);right:-1px}.bs-tooltip-bottom .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow{top:calc(-1 * var(--bs-tooltip-arrow-height))}.bs-tooltip-bottom .tooltip-arrow:before,.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow:before{border-width:0 calc(var(--bs-tooltip-arrow-width) * .5) var(--bs-tooltip-arrow-height);border-bottom-color:var(--bs-tooltip-bg);bottom:-1px}.bs-tooltip-start .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow{right:calc(-1 * var(--bs-tooltip-arrow-height));width:var(--bs-tooltip-arrow-height);height:var(--bs-tooltip-arrow-width)}.bs-tooltip-start .tooltip-arrow:before,.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow:before{border-width:calc(var(--bs-tooltip-arrow-width) * .5) 0 calc(var(--bs-tooltip-arrow-width) * .5) var(--bs-tooltip-arrow-height);border-left-color:var(--bs-tooltip-bg);left:-1px}.tooltip-inner{max-width:var(--bs-tooltip-max-width);padding:var(--bs-tooltip-padding-y) var(--bs-tooltip-padding-x);color:var(--bs-tooltip-color);text-align:center;background-color:var(--bs-tooltip-bg);border-radius:var(--bs-tooltip-border-radius)}.popover{--bs-popover-zindex:1070;--bs-popover-max-width:276px;--bs-popover-font-size:.875rem;--bs-popover-bg:var(--bs-body-bg);--bs-popover-border-width:var(--bs-border-width);--bs-popover-border-color:var(--bs-border-color-translucent);--bs-popover-border-radius:var(--bs-border-radius-lg);--bs-popover-inner-border-radius:calc(var(--bs-border-radius-lg) - var(--bs-border-width));--bs-popover-box-shadow:var(--bs-box-shadow);--bs-popover-header-padding-x:1rem;--bs-popover-header-padding-y:.5rem;--bs-popover-header-font-size:1rem;--bs-popover-header-color:inherit;--bs-popover-header-bg:var(--bs-secondary-bg);--bs-popover-body-padding-x:1rem;--bs-popover-body-padding-y:1rem;--bs-popover-body-color:var(--bs-body-color);--bs-popover-arrow-width:1rem;--bs-popover-arrow-height:.5rem;--bs-popover-arrow-border:var(--bs-popover-border-color);z-index:var(--bs-popover-zindex);max-width:var(--bs-popover-max-width);font-family:var(--bs-font-sans-serif);text-align:left;text-align:start;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;white-space:normal;word-spacing:normal;line-break:auto;font-style:normal;font-weight:400;line-height:1.5;font-size:var(--bs-popover-font-size);word-wrap:break-word;background-color:var(--bs-popover-bg);border:var(--bs-popover-border-width) solid var(--bs-popover-border-color);border-radius:var(--bs-popover-border-radius);background-clip:padding-box;text-decoration:none;display:block}.popover .popover-arrow{width:var(--bs-popover-arrow-width);height:var(--bs-popover-arrow-height);display:block}.popover .popover-arrow:before,.popover .popover-arrow:after{content:"";border:0 solid #0000;display:block;position:absolute}.bs-popover-top>.popover-arrow,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow{bottom:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width))}.bs-popover-top>.popover-arrow:before,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow:before,.bs-popover-top>.popover-arrow:after,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow:after{border-width:var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * .5) 0}.bs-popover-top>.popover-arrow:before,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow:before{border-top-color:var(--bs-popover-arrow-border);bottom:0}.bs-popover-top>.popover-arrow:after,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow:after{bottom:var(--bs-popover-border-width);border-top-color:var(--bs-popover-bg)}.bs-popover-end>.popover-arrow,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow{left:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));width:var(--bs-popover-arrow-height);height:var(--bs-popover-arrow-width)}.bs-popover-end>.popover-arrow:before,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow:before,.bs-popover-end>.popover-arrow:after,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow:after{border-width:calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * .5) 0}.bs-popover-end>.popover-arrow:before,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow:before{border-right-color:var(--bs-popover-arrow-border);left:0}.bs-popover-end>.popover-arrow:after,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow:after{left:var(--bs-popover-border-width);border-right-color:var(--bs-popover-bg)}.bs-popover-bottom>.popover-arrow,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow{top:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width))}.bs-popover-bottom>.popover-arrow:before,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow:before,.bs-popover-bottom>.popover-arrow:after,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow:after{border-width:0 calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height)}.bs-popover-bottom>.popover-arrow:before,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow:before{border-bottom-color:var(--bs-popover-arrow-border);top:0}.bs-popover-bottom>.popover-arrow:after,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow:after{top:var(--bs-popover-border-width);border-bottom-color:var(--bs-popover-bg)}.bs-popover-bottom .popover-header:before,.bs-popover-auto[data-popper-placement^=bottom] .popover-header:before{width:var(--bs-popover-arrow-width);margin-left:calc(-.5 * var(--bs-popover-arrow-width));content:"";border-bottom:var(--bs-popover-border-width) solid var(--bs-popover-header-bg);display:block;position:absolute;top:0;left:50%}.bs-popover-start>.popover-arrow,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow{right:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));width:var(--bs-popover-arrow-height);height:var(--bs-popover-arrow-width)}.bs-popover-start>.popover-arrow:before,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow:before,.bs-popover-start>.popover-arrow:after,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow:after{border-width:calc(var(--bs-popover-arrow-width) * .5) 0 calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height)}.bs-popover-start>.popover-arrow:before,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow:before{border-left-color:var(--bs-popover-arrow-border);right:0}.bs-popover-start>.popover-arrow:after,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow:after{right:var(--bs-popover-border-width);border-left-color:var(--bs-popover-bg)}.popover-header{padding:var(--bs-popover-header-padding-y) var(--bs-popover-header-padding-x);font-size:var(--bs-popover-header-font-size);color:var(--bs-popover-header-color);background-color:var(--bs-popover-header-bg);border-bottom:var(--bs-popover-border-width) solid var(--bs-popover-border-color);border-top-left-radius:var(--bs-popover-inner-border-radius);border-top-right-radius:var(--bs-popover-inner-border-radius);margin-bottom:0}.popover-header:empty{display:none}.popover-body{padding:var(--bs-popover-body-padding-y) var(--bs-popover-body-padding-x);color:var(--bs-popover-body-color)}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{width:100%;position:relative;overflow:hidden}.carousel-inner:after{clear:both;content:"";display:block}.carousel-item{float:left;backface-visibility:hidden;width:100%;margin-right:-100%;transition:transform .6s ease-in-out;display:none;position:relative}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item.active,.carousel-item-next,.carousel-item-prev{display:block}.carousel-item-next:not(.carousel-item-start),.active.carousel-item-end{transform:translate(100%)}.carousel-item-prev:not(.carousel-item-end),.active.carousel-item-start{transform:translate(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item.active,.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end{z-index:1;opacity:1}.carousel-fade .active.carousel-item-start,.carousel-fade .active.carousel-item-end{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-start,.carousel-fade .active.carousel-item-end{transition:none}}.carousel-control-prev,.carousel-control-next{z-index:1;color:#fff;text-align:center;width:15%;filter:var(--bs-carousel-control-icon-filter);opacity:.5;background:0 0;border:0;justify-content:center;align-items:center;padding:0;transition:opacity .15s;display:flex;position:absolute;top:0;bottom:0}@media (prefers-reduced-motion:reduce){.carousel-control-prev,.carousel-control-next{transition:none}}.carousel-control-prev:hover,.carousel-control-prev:focus,.carousel-control-next:hover,.carousel-control-next:focus{color:#fff;opacity:.9;outline:0;text-decoration:none}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-prev-icon,.carousel-control-next-icon{background-position:50%;background-repeat:no-repeat;background-size:100% 100%;width:2rem;height:2rem;display:inline-block}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708'/%3e%3c/svg%3e")}.carousel-indicators{z-index:2;justify-content:center;margin-bottom:1rem;margin-left:15%;margin-right:15%;padding:0;display:flex;position:absolute;bottom:0;left:0;right:0}.carousel-indicators [data-bs-target]{box-sizing:content-box;text-indent:-999px;cursor:pointer;background-color:var(--bs-carousel-indicator-active-bg);opacity:.5;background-clip:padding-box;border:10px solid #0000;border-left:0;border-right:0;flex:0 auto;width:30px;height:3px;margin-left:3px;margin-right:3px;padding:0;transition:opacity .6s}@media (prefers-reduced-motion:reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{color:var(--bs-carousel-caption-color);text-align:center;padding-top:1.25rem;padding-bottom:1.25rem;position:absolute;bottom:1.25rem;left:15%;right:15%}.carousel-dark{--bs-carousel-indicator-active-bg:#000;--bs-carousel-caption-color:#000;--bs-carousel-control-icon-filter:invert(1) grayscale(100)}:root,[data-bs-theme=light]{--bs-carousel-indicator-active-bg:#fff;--bs-carousel-caption-color:#fff;--bs-carousel-control-icon-filter: }[data-bs-theme=dark]{--bs-carousel-indicator-active-bg:#000;--bs-carousel-caption-color:#000;--bs-carousel-control-icon-filter:invert(1) grayscale(100)}.spinner-grow,.spinner-border{width:var(--bs-spinner-width);height:var(--bs-spinner-height);vertical-align:var(--bs-spinner-vertical-align);animation:var(--bs-spinner-animation-speed) linear infinite var(--bs-spinner-animation-name);border-radius:50%;flex-shrink:0;display:inline-block}@keyframes spinner-border{to{transform:rotate(360deg)}}.spinner-border{--bs-spinner-width:2rem;--bs-spinner-height:2rem;--bs-spinner-vertical-align:-.125em;--bs-spinner-border-width:.25em;--bs-spinner-animation-speed:.75s;--bs-spinner-animation-name:spinner-border;border:var(--bs-spinner-border-width) solid currentcolor;border-right-color:#0000}.spinner-border-sm{--bs-spinner-width:1rem;--bs-spinner-height:1rem;--bs-spinner-border-width:.2em}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{--bs-spinner-width:2rem;--bs-spinner-height:2rem;--bs-spinner-vertical-align:-.125em;--bs-spinner-animation-speed:.75s;--bs-spinner-animation-name:spinner-grow;opacity:0;background-color:currentColor}.spinner-grow-sm{--bs-spinner-width:1rem;--bs-spinner-height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{--bs-spinner-animation-speed:1.5s}}.offcanvas,.offcanvas-xxl,.offcanvas-xl,.offcanvas-lg,.offcanvas-md,.offcanvas-sm{--bs-offcanvas-zindex:1045;--bs-offcanvas-width:400px;--bs-offcanvas-height:30vh;--bs-offcanvas-padding-x:1rem;--bs-offcanvas-padding-y:1rem;--bs-offcanvas-color:var(--bs-body-color);--bs-offcanvas-bg:var(--bs-body-bg);--bs-offcanvas-border-width:var(--bs-border-width);--bs-offcanvas-border-color:var(--bs-border-color-translucent);--bs-offcanvas-box-shadow:var(--bs-box-shadow-sm);--bs-offcanvas-transition:transform .3s ease-in-out;--bs-offcanvas-title-line-height:1.5}@media (width<=575.98px){.offcanvas-sm{z-index:var(--bs-offcanvas-zindex);max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);transition:var(--bs-offcanvas-transition);background-clip:padding-box;outline:0;flex-direction:column;display:flex;position:fixed;bottom:0}}@media (width<=575.98px) and (prefers-reduced-motion:reduce){.offcanvas-sm{transition:none}}@media (width<=575.98px){.offcanvas-sm.offcanvas-start{width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;left:0;transform:translate(-100%)}.offcanvas-sm.offcanvas-end{width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;right:0;transform:translate(100%)}.offcanvas-sm.offcanvas-top{height:var(--bs-offcanvas-height);border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;top:0;left:0;right:0;transform:translateY(-100%)}.offcanvas-sm.offcanvas-bottom{height:var(--bs-offcanvas-height);border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;left:0;right:0;transform:translateY(100%)}.offcanvas-sm.showing,.offcanvas-sm.show:not(.hiding){transform:none}.offcanvas-sm.showing,.offcanvas-sm.hiding,.offcanvas-sm.show{visibility:visible}}@media (width>=576px){.offcanvas-sm{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:#0000!important}.offcanvas-sm .offcanvas-header{display:none}.offcanvas-sm .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible;background-color:#0000!important}}@media (width<=767.98px){.offcanvas-md{z-index:var(--bs-offcanvas-zindex);max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);transition:var(--bs-offcanvas-transition);background-clip:padding-box;outline:0;flex-direction:column;display:flex;position:fixed;bottom:0}}@media (width<=767.98px) and (prefers-reduced-motion:reduce){.offcanvas-md{transition:none}}@media (width<=767.98px){.offcanvas-md.offcanvas-start{width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;left:0;transform:translate(-100%)}.offcanvas-md.offcanvas-end{width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;right:0;transform:translate(100%)}.offcanvas-md.offcanvas-top{height:var(--bs-offcanvas-height);border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;top:0;left:0;right:0;transform:translateY(-100%)}.offcanvas-md.offcanvas-bottom{height:var(--bs-offcanvas-height);border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;left:0;right:0;transform:translateY(100%)}.offcanvas-md.showing,.offcanvas-md.show:not(.hiding){transform:none}.offcanvas-md.showing,.offcanvas-md.hiding,.offcanvas-md.show{visibility:visible}}@media (width>=768px){.offcanvas-md{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:#0000!important}.offcanvas-md .offcanvas-header{display:none}.offcanvas-md .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible;background-color:#0000!important}}@media (width<=991.98px){.offcanvas-lg{z-index:var(--bs-offcanvas-zindex);max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);transition:var(--bs-offcanvas-transition);background-clip:padding-box;outline:0;flex-direction:column;display:flex;position:fixed;bottom:0}}@media (width<=991.98px) and (prefers-reduced-motion:reduce){.offcanvas-lg{transition:none}}@media (width<=991.98px){.offcanvas-lg.offcanvas-start{width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;left:0;transform:translate(-100%)}.offcanvas-lg.offcanvas-end{width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;right:0;transform:translate(100%)}.offcanvas-lg.offcanvas-top{height:var(--bs-offcanvas-height);border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;top:0;left:0;right:0;transform:translateY(-100%)}.offcanvas-lg.offcanvas-bottom{height:var(--bs-offcanvas-height);border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;left:0;right:0;transform:translateY(100%)}.offcanvas-lg.showing,.offcanvas-lg.show:not(.hiding){transform:none}.offcanvas-lg.showing,.offcanvas-lg.hiding,.offcanvas-lg.show{visibility:visible}}@media (width>=992px){.offcanvas-lg{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:#0000!important}.offcanvas-lg .offcanvas-header{display:none}.offcanvas-lg .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible;background-color:#0000!important}}@media (width<=1199.98px){.offcanvas-xl{z-index:var(--bs-offcanvas-zindex);max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);transition:var(--bs-offcanvas-transition);background-clip:padding-box;outline:0;flex-direction:column;display:flex;position:fixed;bottom:0}}@media (width<=1199.98px) and (prefers-reduced-motion:reduce){.offcanvas-xl{transition:none}}@media (width<=1199.98px){.offcanvas-xl.offcanvas-start{width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;left:0;transform:translate(-100%)}.offcanvas-xl.offcanvas-end{width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;right:0;transform:translate(100%)}.offcanvas-xl.offcanvas-top{height:var(--bs-offcanvas-height);border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;top:0;left:0;right:0;transform:translateY(-100%)}.offcanvas-xl.offcanvas-bottom{height:var(--bs-offcanvas-height);border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;left:0;right:0;transform:translateY(100%)}.offcanvas-xl.showing,.offcanvas-xl.show:not(.hiding){transform:none}.offcanvas-xl.showing,.offcanvas-xl.hiding,.offcanvas-xl.show{visibility:visible}}@media (width>=1200px){.offcanvas-xl{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:#0000!important}.offcanvas-xl .offcanvas-header{display:none}.offcanvas-xl .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible;background-color:#0000!important}}@media (width<=1399.98px){.offcanvas-xxl{z-index:var(--bs-offcanvas-zindex);max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);transition:var(--bs-offcanvas-transition);background-clip:padding-box;outline:0;flex-direction:column;display:flex;position:fixed;bottom:0}}@media (width<=1399.98px) and (prefers-reduced-motion:reduce){.offcanvas-xxl{transition:none}}@media (width<=1399.98px){.offcanvas-xxl.offcanvas-start{width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;left:0;transform:translate(-100%)}.offcanvas-xxl.offcanvas-end{width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;right:0;transform:translate(100%)}.offcanvas-xxl.offcanvas-top{height:var(--bs-offcanvas-height);border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;top:0;left:0;right:0;transform:translateY(-100%)}.offcanvas-xxl.offcanvas-bottom{height:var(--bs-offcanvas-height);border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;left:0;right:0;transform:translateY(100%)}.offcanvas-xxl.showing,.offcanvas-xxl.show:not(.hiding){transform:none}.offcanvas-xxl.showing,.offcanvas-xxl.hiding,.offcanvas-xxl.show{visibility:visible}}@media (width>=1400px){.offcanvas-xxl{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:#0000!important}.offcanvas-xxl .offcanvas-header{display:none}.offcanvas-xxl .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible;background-color:#0000!important}}.offcanvas{z-index:var(--bs-offcanvas-zindex);max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);transition:var(--bs-offcanvas-transition);background-clip:padding-box;outline:0;flex-direction:column;display:flex;position:fixed;bottom:0}@media (prefers-reduced-motion:reduce){.offcanvas{transition:none}}.offcanvas.offcanvas-start{width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;left:0;transform:translate(-100%)}.offcanvas.offcanvas-end{width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;right:0;transform:translate(100%)}.offcanvas.offcanvas-top{height:var(--bs-offcanvas-height);border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;top:0;left:0;right:0;transform:translateY(-100%)}.offcanvas.offcanvas-bottom{height:var(--bs-offcanvas-height);border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;left:0;right:0;transform:translateY(100%)}.offcanvas.showing,.offcanvas.show:not(.hiding){transform:none}.offcanvas.showing,.offcanvas.hiding,.offcanvas.show{visibility:visible}.offcanvas-backdrop{z-index:1040;background-color:#000;width:100vw;height:100vh;position:fixed;top:0;left:0}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{padding:var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x);align-items:center;display:flex}.offcanvas-header .btn-close{padding:calc(var(--bs-offcanvas-padding-y) * .5) calc(var(--bs-offcanvas-padding-x) * .5);margin-top:calc(-.5 * var(--bs-offcanvas-padding-y));margin-right:calc(-.5 * var(--bs-offcanvas-padding-x));margin-bottom:calc(-.5 * var(--bs-offcanvas-padding-y));margin-left:auto}.offcanvas-title{line-height:var(--bs-offcanvas-title-line-height);margin-bottom:0}.offcanvas-body{padding:var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x);flex-grow:1;overflow-y:auto}.placeholder{vertical-align:middle;cursor:wait;opacity:.5;background-color:currentColor;min-height:1em;display:inline-block}.placeholder.btn:before{content:"";display:inline-block}.placeholder-xs{min-height:.6em}.placeholder-sm{min-height:.8em}.placeholder-lg{min-height:1.2em}.placeholder-glow .placeholder{animation:2s ease-in-out infinite placeholder-glow}@keyframes placeholder-glow{50%{opacity:.2}}.placeholder-wave{animation:2s linear infinite placeholder-wave;-webkit-mask-image:linear-gradient(130deg,#000 55%,#000c 75%,#000 95%);mask-image:linear-gradient(130deg,#000 55%,#000c 75%,#000 95%);-webkit-mask-size:200% 100%;mask-size:200% 100%}@keyframes placeholder-wave{to{-webkit-mask-position:-200% 0;mask-position:-200% 0}}.clearfix:after{clear:both;content:"";display:block}.text-bg-primary{color:#fff!important;background-color:RGBA(var(--bs-primary-rgb), var(--bs-bg-opacity,1))!important}.text-bg-secondary{color:#fff!important;background-color:RGBA(var(--bs-secondary-rgb), var(--bs-bg-opacity,1))!important}.text-bg-success{color:#fff!important;background-color:RGBA(var(--bs-success-rgb), var(--bs-bg-opacity,1))!important}.text-bg-info{color:#000!important;background-color:RGBA(var(--bs-info-rgb), var(--bs-bg-opacity,1))!important}.text-bg-warning{color:#000!important;background-color:RGBA(var(--bs-warning-rgb), var(--bs-bg-opacity,1))!important}.text-bg-danger{color:#fff!important;background-color:RGBA(var(--bs-danger-rgb), var(--bs-bg-opacity,1))!important}.text-bg-light{color:#000!important;background-color:RGBA(var(--bs-light-rgb), var(--bs-bg-opacity,1))!important}.text-bg-dark{color:#fff!important;background-color:RGBA(var(--bs-dark-rgb), var(--bs-bg-opacity,1))!important}.link-primary{color:RGBA(var(--bs-primary-rgb), var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-primary-rgb), var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-primary-rgb), var(--bs-link-underline-opacity,1))!important}.link-primary:hover,.link-primary:focus{color:RGBA(10, 88, 202, var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(10, 88, 202, var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(10, 88, 202, var(--bs-link-underline-opacity,1))!important}.link-secondary{color:RGBA(var(--bs-secondary-rgb), var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-secondary-rgb), var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-secondary-rgb), var(--bs-link-underline-opacity,1))!important}.link-secondary:hover,.link-secondary:focus{color:RGBA(86, 94, 100, var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(86, 94, 100, var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(86, 94, 100, var(--bs-link-underline-opacity,1))!important}.link-success{color:RGBA(var(--bs-success-rgb), var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-success-rgb), var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-success-rgb), var(--bs-link-underline-opacity,1))!important}.link-success:hover,.link-success:focus{color:RGBA(20, 108, 67, var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(20, 108, 67, var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(20, 108, 67, var(--bs-link-underline-opacity,1))!important}.link-info{color:RGBA(var(--bs-info-rgb), var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-info-rgb), var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-info-rgb), var(--bs-link-underline-opacity,1))!important}.link-info:hover,.link-info:focus{color:RGBA(61, 213, 243, var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(61, 213, 243, var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(61, 213, 243, var(--bs-link-underline-opacity,1))!important}.link-warning{color:RGBA(var(--bs-warning-rgb), var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-warning-rgb), var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-warning-rgb), var(--bs-link-underline-opacity,1))!important}.link-warning:hover,.link-warning:focus{color:RGBA(255, 205, 57, var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(255, 205, 57, var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(255, 205, 57, var(--bs-link-underline-opacity,1))!important}.link-danger{color:RGBA(var(--bs-danger-rgb), var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-danger-rgb), var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-danger-rgb), var(--bs-link-underline-opacity,1))!important}.link-danger:hover,.link-danger:focus{color:RGBA(176, 42, 55, var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(176, 42, 55, var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(176, 42, 55, var(--bs-link-underline-opacity,1))!important}.link-light{color:RGBA(var(--bs-light-rgb), var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-light-rgb), var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-light-rgb), var(--bs-link-underline-opacity,1))!important}.link-light:hover,.link-light:focus{color:RGBA(249, 250, 251, var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(249, 250, 251, var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(249, 250, 251, var(--bs-link-underline-opacity,1))!important}.link-dark{color:RGBA(var(--bs-dark-rgb), var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-dark-rgb), var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-dark-rgb), var(--bs-link-underline-opacity,1))!important}.link-dark:hover,.link-dark:focus{color:RGBA(26, 30, 33, var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(26, 30, 33, var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(26, 30, 33, var(--bs-link-underline-opacity,1))!important}.link-body-emphasis{color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity,1))!important}.link-body-emphasis:hover,.link-body-emphasis:focus{color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-opacity,.75))!important;-webkit-text-decoration-color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity,.75))!important;text-decoration-color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity,.75))!important}.focus-ring:focus{box-shadow:var(--bs-focus-ring-x,0) var(--bs-focus-ring-y,0) var(--bs-focus-ring-blur,0) var(--bs-focus-ring-width) var(--bs-focus-ring-color);outline:0}.icon-link{-webkit-text-decoration-color:rgba(var(--bs-link-color-rgb), var(--bs-link-opacity,.5));text-decoration-color:rgba(var(--bs-link-color-rgb), var(--bs-link-opacity,.5));text-underline-offset:.25em;backface-visibility:hidden;align-items:center;gap:.375rem;display:inline-flex}.icon-link>.bi{fill:currentColor;flex-shrink:0;width:1em;height:1em;transition:transform .2s ease-in-out}@media (prefers-reduced-motion:reduce){.icon-link>.bi{transition:none}}.icon-link-hover:hover>.bi,.icon-link-hover:focus-visible>.bi{transform:var(--bs-icon-link-transform,translate3d(.25em, 0, 0))}.ratio{width:100%;position:relative}.ratio:before{padding-top:var(--bs-aspect-ratio);content:"";display:block}.ratio>*{width:100%;height:100%;position:absolute;top:0;left:0}.ratio-1x1{--bs-aspect-ratio:100%}.ratio-4x3{--bs-aspect-ratio:75%}.ratio-16x9{--bs-aspect-ratio:56.25%}.ratio-21x9{--bs-aspect-ratio:42.8571%}.fixed-top{z-index:1030;position:fixed;top:0;left:0;right:0}.fixed-bottom{z-index:1030;position:fixed;bottom:0;left:0;right:0}.sticky-top{z-index:1020;position:sticky;top:0}.sticky-bottom{z-index:1020;position:sticky;bottom:0}@media (width>=576px){.sticky-sm-top{z-index:1020;position:sticky;top:0}.sticky-sm-bottom{z-index:1020;position:sticky;bottom:0}}@media (width>=768px){.sticky-md-top{z-index:1020;position:sticky;top:0}.sticky-md-bottom{z-index:1020;position:sticky;bottom:0}}@media (width>=992px){.sticky-lg-top{z-index:1020;position:sticky;top:0}.sticky-lg-bottom{z-index:1020;position:sticky;bottom:0}}@media (width>=1200px){.sticky-xl-top{z-index:1020;position:sticky;top:0}.sticky-xl-bottom{z-index:1020;position:sticky;bottom:0}}@media (width>=1400px){.sticky-xxl-top{z-index:1020;position:sticky;top:0}.sticky-xxl-bottom{z-index:1020;position:sticky;bottom:0}}.hstack{flex-direction:row;align-self:stretch;align-items:center;display:flex}.vstack{flex-direction:column;flex:auto;align-self:stretch;display:flex}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){clip:rect(0, 0, 0, 0)!important;white-space:nowrap!important;border:0!important;width:1px!important;height:1px!important;margin:-1px!important;padding:0!important;overflow:hidden!important}.visually-hidden:not(caption),.visually-hidden-focusable:not(:focus):not(:focus-within):not(caption){position:absolute!important}.visually-hidden *,.visually-hidden-focusable:not(:focus):not(:focus-within) *{overflow:hidden!important}.stretched-link:after{z-index:1;content:"";position:absolute;inset:0}.text-truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.vr{width:var(--bs-border-width);opacity:.25;background-color:currentColor;align-self:stretch;min-height:1em;display:inline-block}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.float-start{float:left!important}.float-end{float:right!important}.float-none{float:none!important}.object-fit-contain{object-fit:contain!important}.object-fit-cover{object-fit:cover!important}.object-fit-fill{object-fit:fill!important}.object-fit-scale{object-fit:scale-down!important}.object-fit-none{object-fit:none!important}.opacity-0{opacity:0!important}.opacity-25{opacity:.25!important}.opacity-50{opacity:.5!important}.opacity-75{opacity:.75!important}.opacity-100{opacity:1!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-visible{overflow:visible!important}.overflow-scroll{overflow:scroll!important}.overflow-x-auto{overflow-x:auto!important}.overflow-x-hidden{overflow-x:hidden!important}.overflow-x-visible{overflow-x:visible!important}.overflow-x-scroll{overflow-x:scroll!important}.overflow-y-auto{overflow-y:auto!important}.overflow-y-hidden{overflow-y:hidden!important}.overflow-y-visible{overflow-y:visible!important}.overflow-y-scroll{overflow-y:scroll!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-grid{display:grid!important}.d-inline-grid{display:inline-grid!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}.d-none{display:none!important}.shadow{box-shadow:var(--bs-box-shadow)!important}.shadow-sm{box-shadow:var(--bs-box-shadow-sm)!important}.shadow-lg{box-shadow:var(--bs-box-shadow-lg)!important}.shadow-none{box-shadow:none!important}.focus-ring-primary{--bs-focus-ring-color:rgba(var(--bs-primary-rgb), var(--bs-focus-ring-opacity))}.focus-ring-secondary{--bs-focus-ring-color:rgba(var(--bs-secondary-rgb), var(--bs-focus-ring-opacity))}.focus-ring-success{--bs-focus-ring-color:rgba(var(--bs-success-rgb), var(--bs-focus-ring-opacity))}.focus-ring-info{--bs-focus-ring-color:rgba(var(--bs-info-rgb), var(--bs-focus-ring-opacity))}.focus-ring-warning{--bs-focus-ring-color:rgba(var(--bs-warning-rgb), var(--bs-focus-ring-opacity))}.focus-ring-danger{--bs-focus-ring-color:rgba(var(--bs-danger-rgb), var(--bs-focus-ring-opacity))}.focus-ring-light{--bs-focus-ring-color:rgba(var(--bs-light-rgb), var(--bs-focus-ring-opacity))}.focus-ring-dark{--bs-focus-ring-color:rgba(var(--bs-dark-rgb), var(--bs-focus-ring-opacity))}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:sticky!important}.top-0{top:0!important}.top-50{top:50%!important}.top-100{top:100%!important}.bottom-0{bottom:0!important}.bottom-50{bottom:50%!important}.bottom-100{bottom:100%!important}.start-0{left:0!important}.start-50{left:50%!important}.start-100{left:100%!important}.end-0{right:0!important}.end-50{right:50%!important}.end-100{right:100%!important}.translate-middle{transform:translate(-50%,-50%)!important}.translate-middle-x{transform:translate(-50%)!important}.translate-middle-y{transform:translateY(-50%)!important}.border{border:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-0{border:0!important}.border-top{border-top:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-top-0{border-top:0!important}.border-end{border-right:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-end-0{border-right:0!important}.border-bottom{border-bottom:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-bottom-0{border-bottom:0!important}.border-start{border-left:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-start-0{border-left:0!important}.border-primary{--bs-border-opacity:1;border-color:rgba(var(--bs-primary-rgb), var(--bs-border-opacity))!important}.border-secondary{--bs-border-opacity:1;border-color:rgba(var(--bs-secondary-rgb), var(--bs-border-opacity))!important}.border-success{--bs-border-opacity:1;border-color:rgba(var(--bs-success-rgb), var(--bs-border-opacity))!important}.border-info{--bs-border-opacity:1;border-color:rgba(var(--bs-info-rgb), var(--bs-border-opacity))!important}.border-warning{--bs-border-opacity:1;border-color:rgba(var(--bs-warning-rgb), var(--bs-border-opacity))!important}.border-danger{--bs-border-opacity:1;border-color:rgba(var(--bs-danger-rgb), var(--bs-border-opacity))!important}.border-light{--bs-border-opacity:1;border-color:rgba(var(--bs-light-rgb), var(--bs-border-opacity))!important}.border-dark{--bs-border-opacity:1;border-color:rgba(var(--bs-dark-rgb), var(--bs-border-opacity))!important}.border-black{--bs-border-opacity:1;border-color:rgba(var(--bs-black-rgb), var(--bs-border-opacity))!important}.border-white{--bs-border-opacity:1;border-color:rgba(var(--bs-white-rgb), var(--bs-border-opacity))!important}.border-primary-subtle{border-color:var(--bs-primary-border-subtle)!important}.border-secondary-subtle{border-color:var(--bs-secondary-border-subtle)!important}.border-success-subtle{border-color:var(--bs-success-border-subtle)!important}.border-info-subtle{border-color:var(--bs-info-border-subtle)!important}.border-warning-subtle{border-color:var(--bs-warning-border-subtle)!important}.border-danger-subtle{border-color:var(--bs-danger-border-subtle)!important}.border-light-subtle{border-color:var(--bs-light-border-subtle)!important}.border-dark-subtle{border-color:var(--bs-dark-border-subtle)!important}.border-1{border-width:1px!important}.border-2{border-width:2px!important}.border-3{border-width:3px!important}.border-4{border-width:4px!important}.border-5{border-width:5px!important}.border-opacity-10{--bs-border-opacity:.1}.border-opacity-25{--bs-border-opacity:.25}.border-opacity-50{--bs-border-opacity:.5}.border-opacity-75{--bs-border-opacity:.75}.border-opacity-100{--bs-border-opacity:1}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.mw-100{max-width:100%!important}.vw-100{width:100vw!important}.min-vw-100{min-width:100vw!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mh-100{max-height:100%!important}.vh-100{height:100vh!important}.min-vh-100{min-height:100vh!important}.flex-fill{flex:auto!important}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.justify-content-evenly{justify-content:space-evenly!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}.order-first{order:-1!important}.order-0{order:0!important}.order-1{order:1!important}.order-2{order:2!important}.order-3{order:3!important}.order-4{order:4!important}.order-5{order:5!important}.order-last{order:6!important}.m-0{margin:0!important}.m-1{margin:.25rem!important}.m-2{margin:.5rem!important}.m-3{margin:1rem!important}.m-4{margin:1.5rem!important}.m-5{margin:3rem!important}.m-auto{margin:auto!important}.mx-0{margin-left:0!important;margin-right:0!important}.mx-1{margin-left:.25rem!important;margin-right:.25rem!important}.mx-2{margin-left:.5rem!important;margin-right:.5rem!important}.mx-3{margin-left:1rem!important;margin-right:1rem!important}.mx-4{margin-left:1.5rem!important;margin-right:1.5rem!important}.mx-5{margin-left:3rem!important;margin-right:3rem!important}.mx-auto{margin-left:auto!important;margin-right:auto!important}.my-0{margin-top:0!important;margin-bottom:0!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-0{margin-top:0!important}.mt-1{margin-top:.25rem!important}.mt-2{margin-top:.5rem!important}.mt-3{margin-top:1rem!important}.mt-4{margin-top:1.5rem!important}.mt-5{margin-top:3rem!important}.mt-auto{margin-top:auto!important}.me-0{margin-right:0!important}.me-1{margin-right:.25rem!important}.me-2{margin-right:.5rem!important}.me-3{margin-right:1rem!important}.me-4{margin-right:1.5rem!important}.me-5{margin-right:3rem!important}.me-auto{margin-right:auto!important}.mb-0{margin-bottom:0!important}.mb-1{margin-bottom:.25rem!important}.mb-2{margin-bottom:.5rem!important}.mb-3{margin-bottom:1rem!important}.mb-4{margin-bottom:1.5rem!important}.mb-5{margin-bottom:3rem!important}.mb-auto{margin-bottom:auto!important}.ms-0{margin-left:0!important}.ms-1{margin-left:.25rem!important}.ms-2{margin-left:.5rem!important}.ms-3{margin-left:1rem!important}.ms-4{margin-left:1.5rem!important}.ms-5{margin-left:3rem!important}.ms-auto{margin-left:auto!important}.p-0{padding:0!important}.p-1{padding:.25rem!important}.p-2{padding:.5rem!important}.p-3{padding:1rem!important}.p-4{padding:1.5rem!important}.p-5{padding:3rem!important}.px-0{padding-left:0!important;padding-right:0!important}.px-1{padding-left:.25rem!important;padding-right:.25rem!important}.px-2{padding-left:.5rem!important;padding-right:.5rem!important}.px-3{padding-left:1rem!important;padding-right:1rem!important}.px-4{padding-left:1.5rem!important;padding-right:1.5rem!important}.px-5{padding-left:3rem!important;padding-right:3rem!important}.py-0{padding-top:0!important;padding-bottom:0!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-0{padding-top:0!important}.pt-1{padding-top:.25rem!important}.pt-2{padding-top:.5rem!important}.pt-3{padding-top:1rem!important}.pt-4{padding-top:1.5rem!important}.pt-5{padding-top:3rem!important}.pe-0{padding-right:0!important}.pe-1{padding-right:.25rem!important}.pe-2{padding-right:.5rem!important}.pe-3{padding-right:1rem!important}.pe-4{padding-right:1.5rem!important}.pe-5{padding-right:3rem!important}.pb-0{padding-bottom:0!important}.pb-1{padding-bottom:.25rem!important}.pb-2{padding-bottom:.5rem!important}.pb-3{padding-bottom:1rem!important}.pb-4{padding-bottom:1.5rem!important}.pb-5{padding-bottom:3rem!important}.ps-0{padding-left:0!important}.ps-1{padding-left:.25rem!important}.ps-2{padding-left:.5rem!important}.ps-3{padding-left:1rem!important}.ps-4{padding-left:1.5rem!important}.ps-5{padding-left:3rem!important}.gap-0{gap:0!important}.gap-1{gap:.25rem!important}.gap-2{gap:.5rem!important}.gap-3{gap:1rem!important}.gap-4{gap:1.5rem!important}.gap-5{gap:3rem!important}.row-gap-0{row-gap:0!important}.row-gap-1{row-gap:.25rem!important}.row-gap-2{row-gap:.5rem!important}.row-gap-3{row-gap:1rem!important}.row-gap-4{row-gap:1.5rem!important}.row-gap-5{row-gap:3rem!important}.column-gap-0{column-gap:0!important}.column-gap-1{column-gap:.25rem!important}.column-gap-2{column-gap:.5rem!important}.column-gap-3{column-gap:1rem!important}.column-gap-4{column-gap:1.5rem!important}.column-gap-5{column-gap:3rem!important}.font-monospace{font-family:var(--bs-font-monospace)!important}.fs-1{font-size:calc(1.375rem + 1.5vw)!important}.fs-2{font-size:calc(1.325rem + .9vw)!important}.fs-3{font-size:calc(1.3rem + .6vw)!important}.fs-4{font-size:calc(1.275rem + .3vw)!important}.fs-5{font-size:1.25rem!important}.fs-6{font-size:1rem!important}.fst-italic{font-style:italic!important}.fst-normal{font-style:normal!important}.fw-lighter{font-weight:lighter!important}.fw-light{font-weight:300!important}.fw-normal{font-weight:400!important}.fw-medium{font-weight:500!important}.fw-semibold{font-weight:600!important}.fw-bold{font-weight:700!important}.fw-bolder{font-weight:bolder!important}.lh-1{line-height:1!important}.lh-sm{line-height:1.25!important}.lh-base{line-height:1.5!important}.lh-lg{line-height:2!important}.text-start{text-align:left!important}.text-end{text-align:right!important}.text-center{text-align:center!important}.text-decoration-none{text-decoration:none!important}.text-decoration-underline{text-decoration:underline!important}.text-decoration-line-through{text-decoration:line-through!important}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-break{word-wrap:break-word!important;word-break:break-word!important}.text-primary{--bs-text-opacity:1;color:rgba(var(--bs-primary-rgb), var(--bs-text-opacity))!important}.text-secondary{--bs-text-opacity:1;color:rgba(var(--bs-secondary-rgb), var(--bs-text-opacity))!important}.text-success{--bs-text-opacity:1;color:rgba(var(--bs-success-rgb), var(--bs-text-opacity))!important}.text-info{--bs-text-opacity:1;color:rgba(var(--bs-info-rgb), var(--bs-text-opacity))!important}.text-warning{--bs-text-opacity:1;color:rgba(var(--bs-warning-rgb), var(--bs-text-opacity))!important}.text-danger{--bs-text-opacity:1;color:rgba(var(--bs-danger-rgb), var(--bs-text-opacity))!important}.text-light{--bs-text-opacity:1;color:rgba(var(--bs-light-rgb), var(--bs-text-opacity))!important}.text-dark{--bs-text-opacity:1;color:rgba(var(--bs-dark-rgb), var(--bs-text-opacity))!important}.text-black{--bs-text-opacity:1;color:rgba(var(--bs-black-rgb), var(--bs-text-opacity))!important}.text-white{--bs-text-opacity:1;color:rgba(var(--bs-white-rgb), var(--bs-text-opacity))!important}.text-body{--bs-text-opacity:1;color:rgba(var(--bs-body-color-rgb), var(--bs-text-opacity))!important}.text-muted{--bs-text-opacity:1;color:var(--bs-secondary-color)!important}.text-black-50{--bs-text-opacity:1;color:#00000080!important}.text-white-50{--bs-text-opacity:1;color:#ffffff80!important}.text-body-secondary{--bs-text-opacity:1;color:var(--bs-secondary-color)!important}.text-body-tertiary{--bs-text-opacity:1;color:var(--bs-tertiary-color)!important}.text-body-emphasis{--bs-text-opacity:1;color:var(--bs-emphasis-color)!important}.text-reset{--bs-text-opacity:1;color:inherit!important}.text-opacity-25{--bs-text-opacity:.25}.text-opacity-50{--bs-text-opacity:.5}.text-opacity-75{--bs-text-opacity:.75}.text-opacity-100{--bs-text-opacity:1}.text-primary-emphasis{color:var(--bs-primary-text-emphasis)!important}.text-secondary-emphasis{color:var(--bs-secondary-text-emphasis)!important}.text-success-emphasis{color:var(--bs-success-text-emphasis)!important}.text-info-emphasis{color:var(--bs-info-text-emphasis)!important}.text-warning-emphasis{color:var(--bs-warning-text-emphasis)!important}.text-danger-emphasis{color:var(--bs-danger-text-emphasis)!important}.text-light-emphasis{color:var(--bs-light-text-emphasis)!important}.text-dark-emphasis{color:var(--bs-dark-text-emphasis)!important}.link-opacity-10,.link-opacity-10-hover:hover{--bs-link-opacity:.1}.link-opacity-25,.link-opacity-25-hover:hover{--bs-link-opacity:.25}.link-opacity-50,.link-opacity-50-hover:hover{--bs-link-opacity:.5}.link-opacity-75,.link-opacity-75-hover:hover{--bs-link-opacity:.75}.link-opacity-100,.link-opacity-100-hover:hover{--bs-link-opacity:1}.link-offset-1,.link-offset-1-hover:hover{text-underline-offset:.125em!important}.link-offset-2,.link-offset-2-hover:hover{text-underline-offset:.25em!important}.link-offset-3,.link-offset-3-hover:hover{text-underline-offset:.375em!important}.link-underline-primary{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-primary-rgb), var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-primary-rgb), var(--bs-link-underline-opacity))!important}.link-underline-secondary{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-secondary-rgb), var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-secondary-rgb), var(--bs-link-underline-opacity))!important}.link-underline-success{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-success-rgb), var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-success-rgb), var(--bs-link-underline-opacity))!important}.link-underline-info{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-info-rgb), var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-info-rgb), var(--bs-link-underline-opacity))!important}.link-underline-warning{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-warning-rgb), var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-warning-rgb), var(--bs-link-underline-opacity))!important}.link-underline-danger{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-danger-rgb), var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-danger-rgb), var(--bs-link-underline-opacity))!important}.link-underline-light{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-light-rgb), var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-light-rgb), var(--bs-link-underline-opacity))!important}.link-underline-dark{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-dark-rgb), var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-dark-rgb), var(--bs-link-underline-opacity))!important}.link-underline{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-link-color-rgb), var(--bs-link-underline-opacity,1))!important;text-decoration-color:rgba(var(--bs-link-color-rgb), var(--bs-link-underline-opacity,1))!important}.link-underline-opacity-0,.link-underline-opacity-0-hover:hover{--bs-link-underline-opacity:0}.link-underline-opacity-10,.link-underline-opacity-10-hover:hover{--bs-link-underline-opacity:.1}.link-underline-opacity-25,.link-underline-opacity-25-hover:hover{--bs-link-underline-opacity:.25}.link-underline-opacity-50,.link-underline-opacity-50-hover:hover{--bs-link-underline-opacity:.5}.link-underline-opacity-75,.link-underline-opacity-75-hover:hover{--bs-link-underline-opacity:.75}.link-underline-opacity-100,.link-underline-opacity-100-hover:hover{--bs-link-underline-opacity:1}.bg-primary{--bs-bg-opacity:1;background-color:rgba(var(--bs-primary-rgb), var(--bs-bg-opacity))!important}.bg-secondary{--bs-bg-opacity:1;background-color:rgba(var(--bs-secondary-rgb), var(--bs-bg-opacity))!important}.bg-success{--bs-bg-opacity:1;background-color:rgba(var(--bs-success-rgb), var(--bs-bg-opacity))!important}.bg-info{--bs-bg-opacity:1;background-color:rgba(var(--bs-info-rgb), var(--bs-bg-opacity))!important}.bg-warning{--bs-bg-opacity:1;background-color:rgba(var(--bs-warning-rgb), var(--bs-bg-opacity))!important}.bg-danger{--bs-bg-opacity:1;background-color:rgba(var(--bs-danger-rgb), var(--bs-bg-opacity))!important}.bg-light{--bs-bg-opacity:1;background-color:rgba(var(--bs-light-rgb), var(--bs-bg-opacity))!important}.bg-dark{--bs-bg-opacity:1;background-color:rgba(var(--bs-dark-rgb), var(--bs-bg-opacity))!important}.bg-black{--bs-bg-opacity:1;background-color:rgba(var(--bs-black-rgb), var(--bs-bg-opacity))!important}.bg-white{--bs-bg-opacity:1;background-color:rgba(var(--bs-white-rgb), var(--bs-bg-opacity))!important}.bg-body{--bs-bg-opacity:1;background-color:rgba(var(--bs-body-bg-rgb), var(--bs-bg-opacity))!important}.bg-transparent{--bs-bg-opacity:1;background-color:#0000!important}.bg-body-secondary{--bs-bg-opacity:1;background-color:rgba(var(--bs-secondary-bg-rgb), var(--bs-bg-opacity))!important}.bg-body-tertiary{--bs-bg-opacity:1;background-color:rgba(var(--bs-tertiary-bg-rgb), var(--bs-bg-opacity))!important}.bg-opacity-10{--bs-bg-opacity:.1}.bg-opacity-25{--bs-bg-opacity:.25}.bg-opacity-50{--bs-bg-opacity:.5}.bg-opacity-75{--bs-bg-opacity:.75}.bg-opacity-100{--bs-bg-opacity:1}.bg-primary-subtle{background-color:var(--bs-primary-bg-subtle)!important}.bg-secondary-subtle{background-color:var(--bs-secondary-bg-subtle)!important}.bg-success-subtle{background-color:var(--bs-success-bg-subtle)!important}.bg-info-subtle{background-color:var(--bs-info-bg-subtle)!important}.bg-warning-subtle{background-color:var(--bs-warning-bg-subtle)!important}.bg-danger-subtle{background-color:var(--bs-danger-bg-subtle)!important}.bg-light-subtle{background-color:var(--bs-light-bg-subtle)!important}.bg-dark-subtle{background-color:var(--bs-dark-bg-subtle)!important}.bg-gradient{background-image:var(--bs-gradient)!important}.user-select-all{-webkit-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;user-select:none!important}.pe-none{pointer-events:none!important}.pe-auto{pointer-events:auto!important}.rounded{border-radius:var(--bs-border-radius)!important}.rounded-0{border-radius:0!important}.rounded-1{border-radius:var(--bs-border-radius-sm)!important}.rounded-2{border-radius:var(--bs-border-radius)!important}.rounded-3{border-radius:var(--bs-border-radius-lg)!important}.rounded-4{border-radius:var(--bs-border-radius-xl)!important}.rounded-5{border-radius:var(--bs-border-radius-xxl)!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:var(--bs-border-radius-pill)!important}.rounded-top{border-top-left-radius:var(--bs-border-radius)!important;border-top-right-radius:var(--bs-border-radius)!important}.rounded-top-0{border-top-left-radius:0!important;border-top-right-radius:0!important}.rounded-top-1{border-top-left-radius:var(--bs-border-radius-sm)!important;border-top-right-radius:var(--bs-border-radius-sm)!important}.rounded-top-2{border-top-left-radius:var(--bs-border-radius)!important;border-top-right-radius:var(--bs-border-radius)!important}.rounded-top-3{border-top-left-radius:var(--bs-border-radius-lg)!important;border-top-right-radius:var(--bs-border-radius-lg)!important}.rounded-top-4{border-top-left-radius:var(--bs-border-radius-xl)!important;border-top-right-radius:var(--bs-border-radius-xl)!important}.rounded-top-5{border-top-left-radius:var(--bs-border-radius-xxl)!important;border-top-right-radius:var(--bs-border-radius-xxl)!important}.rounded-top-circle{border-top-left-radius:50%!important;border-top-right-radius:50%!important}.rounded-top-pill{border-top-left-radius:var(--bs-border-radius-pill)!important;border-top-right-radius:var(--bs-border-radius-pill)!important}.rounded-end{border-top-right-radius:var(--bs-border-radius)!important;border-bottom-right-radius:var(--bs-border-radius)!important}.rounded-end-0{border-top-right-radius:0!important;border-bottom-right-radius:0!important}.rounded-end-1{border-top-right-radius:var(--bs-border-radius-sm)!important;border-bottom-right-radius:var(--bs-border-radius-sm)!important}.rounded-end-2{border-top-right-radius:var(--bs-border-radius)!important;border-bottom-right-radius:var(--bs-border-radius)!important}.rounded-end-3{border-top-right-radius:var(--bs-border-radius-lg)!important;border-bottom-right-radius:var(--bs-border-radius-lg)!important}.rounded-end-4{border-top-right-radius:var(--bs-border-radius-xl)!important;border-bottom-right-radius:var(--bs-border-radius-xl)!important}.rounded-end-5{border-top-right-radius:var(--bs-border-radius-xxl)!important;border-bottom-right-radius:var(--bs-border-radius-xxl)!important}.rounded-end-circle{border-top-right-radius:50%!important;border-bottom-right-radius:50%!important}.rounded-end-pill{border-top-right-radius:var(--bs-border-radius-pill)!important;border-bottom-right-radius:var(--bs-border-radius-pill)!important}.rounded-bottom{border-bottom-right-radius:var(--bs-border-radius)!important;border-bottom-left-radius:var(--bs-border-radius)!important}.rounded-bottom-0{border-bottom-right-radius:0!important;border-bottom-left-radius:0!important}.rounded-bottom-1{border-bottom-right-radius:var(--bs-border-radius-sm)!important;border-bottom-left-radius:var(--bs-border-radius-sm)!important}.rounded-bottom-2{border-bottom-right-radius:var(--bs-border-radius)!important;border-bottom-left-radius:var(--bs-border-radius)!important}.rounded-bottom-3{border-bottom-right-radius:var(--bs-border-radius-lg)!important;border-bottom-left-radius:var(--bs-border-radius-lg)!important}.rounded-bottom-4{border-bottom-right-radius:var(--bs-border-radius-xl)!important;border-bottom-left-radius:var(--bs-border-radius-xl)!important}.rounded-bottom-5{border-bottom-right-radius:var(--bs-border-radius-xxl)!important;border-bottom-left-radius:var(--bs-border-radius-xxl)!important}.rounded-bottom-circle{border-bottom-right-radius:50%!important;border-bottom-left-radius:50%!important}.rounded-bottom-pill{border-bottom-right-radius:var(--bs-border-radius-pill)!important;border-bottom-left-radius:var(--bs-border-radius-pill)!important}.rounded-start{border-bottom-left-radius:var(--bs-border-radius)!important;border-top-left-radius:var(--bs-border-radius)!important}.rounded-start-0{border-top-left-radius:0!important;border-bottom-left-radius:0!important}.rounded-start-1{border-bottom-left-radius:var(--bs-border-radius-sm)!important;border-top-left-radius:var(--bs-border-radius-sm)!important}.rounded-start-2{border-bottom-left-radius:var(--bs-border-radius)!important;border-top-left-radius:var(--bs-border-radius)!important}.rounded-start-3{border-bottom-left-radius:var(--bs-border-radius-lg)!important;border-top-left-radius:var(--bs-border-radius-lg)!important}.rounded-start-4{border-bottom-left-radius:var(--bs-border-radius-xl)!important;border-top-left-radius:var(--bs-border-radius-xl)!important}.rounded-start-5{border-bottom-left-radius:var(--bs-border-radius-xxl)!important;border-top-left-radius:var(--bs-border-radius-xxl)!important}.rounded-start-circle{border-top-left-radius:50%!important;border-bottom-left-radius:50%!important}.rounded-start-pill{border-bottom-left-radius:var(--bs-border-radius-pill)!important;border-top-left-radius:var(--bs-border-radius-pill)!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}.z-n1{z-index:-1!important}.z-0{z-index:0!important}.z-1{z-index:1!important}.z-2{z-index:2!important}.z-3{z-index:3!important}@media (width>=576px){.float-sm-start{float:left!important}.float-sm-end{float:right!important}.float-sm-none{float:none!important}.object-fit-sm-contain{object-fit:contain!important}.object-fit-sm-cover{object-fit:cover!important}.object-fit-sm-fill{object-fit:fill!important}.object-fit-sm-scale{object-fit:scale-down!important}.object-fit-sm-none{object-fit:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-grid{display:grid!important}.d-sm-inline-grid{display:inline-grid!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}.d-sm-none{display:none!important}.flex-sm-fill{flex:auto!important}.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.justify-content-sm-evenly{justify-content:space-evenly!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}.order-sm-first{order:-1!important}.order-sm-0{order:0!important}.order-sm-1{order:1!important}.order-sm-2{order:2!important}.order-sm-3{order:3!important}.order-sm-4{order:4!important}.order-sm-5{order:5!important}.order-sm-last{order:6!important}.m-sm-0{margin:0!important}.m-sm-1{margin:.25rem!important}.m-sm-2{margin:.5rem!important}.m-sm-3{margin:1rem!important}.m-sm-4{margin:1.5rem!important}.m-sm-5{margin:3rem!important}.m-sm-auto{margin:auto!important}.mx-sm-0{margin-left:0!important;margin-right:0!important}.mx-sm-1{margin-left:.25rem!important;margin-right:.25rem!important}.mx-sm-2{margin-left:.5rem!important;margin-right:.5rem!important}.mx-sm-3{margin-left:1rem!important;margin-right:1rem!important}.mx-sm-4{margin-left:1.5rem!important;margin-right:1.5rem!important}.mx-sm-5{margin-left:3rem!important;margin-right:3rem!important}.mx-sm-auto{margin-left:auto!important;margin-right:auto!important}.my-sm-0{margin-top:0!important;margin-bottom:0!important}.my-sm-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-sm-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-sm-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-sm-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-sm-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-sm-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-sm-0{margin-top:0!important}.mt-sm-1{margin-top:.25rem!important}.mt-sm-2{margin-top:.5rem!important}.mt-sm-3{margin-top:1rem!important}.mt-sm-4{margin-top:1.5rem!important}.mt-sm-5{margin-top:3rem!important}.mt-sm-auto{margin-top:auto!important}.me-sm-0{margin-right:0!important}.me-sm-1{margin-right:.25rem!important}.me-sm-2{margin-right:.5rem!important}.me-sm-3{margin-right:1rem!important}.me-sm-4{margin-right:1.5rem!important}.me-sm-5{margin-right:3rem!important}.me-sm-auto{margin-right:auto!important}.mb-sm-0{margin-bottom:0!important}.mb-sm-1{margin-bottom:.25rem!important}.mb-sm-2{margin-bottom:.5rem!important}.mb-sm-3{margin-bottom:1rem!important}.mb-sm-4{margin-bottom:1.5rem!important}.mb-sm-5{margin-bottom:3rem!important}.mb-sm-auto{margin-bottom:auto!important}.ms-sm-0{margin-left:0!important}.ms-sm-1{margin-left:.25rem!important}.ms-sm-2{margin-left:.5rem!important}.ms-sm-3{margin-left:1rem!important}.ms-sm-4{margin-left:1.5rem!important}.ms-sm-5{margin-left:3rem!important}.ms-sm-auto{margin-left:auto!important}.p-sm-0{padding:0!important}.p-sm-1{padding:.25rem!important}.p-sm-2{padding:.5rem!important}.p-sm-3{padding:1rem!important}.p-sm-4{padding:1.5rem!important}.p-sm-5{padding:3rem!important}.px-sm-0{padding-left:0!important;padding-right:0!important}.px-sm-1{padding-left:.25rem!important;padding-right:.25rem!important}.px-sm-2{padding-left:.5rem!important;padding-right:.5rem!important}.px-sm-3{padding-left:1rem!important;padding-right:1rem!important}.px-sm-4{padding-left:1.5rem!important;padding-right:1.5rem!important}.px-sm-5{padding-left:3rem!important;padding-right:3rem!important}.py-sm-0{padding-top:0!important;padding-bottom:0!important}.py-sm-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-sm-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-sm-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-sm-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-sm-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-sm-0{padding-top:0!important}.pt-sm-1{padding-top:.25rem!important}.pt-sm-2{padding-top:.5rem!important}.pt-sm-3{padding-top:1rem!important}.pt-sm-4{padding-top:1.5rem!important}.pt-sm-5{padding-top:3rem!important}.pe-sm-0{padding-right:0!important}.pe-sm-1{padding-right:.25rem!important}.pe-sm-2{padding-right:.5rem!important}.pe-sm-3{padding-right:1rem!important}.pe-sm-4{padding-right:1.5rem!important}.pe-sm-5{padding-right:3rem!important}.pb-sm-0{padding-bottom:0!important}.pb-sm-1{padding-bottom:.25rem!important}.pb-sm-2{padding-bottom:.5rem!important}.pb-sm-3{padding-bottom:1rem!important}.pb-sm-4{padding-bottom:1.5rem!important}.pb-sm-5{padding-bottom:3rem!important}.ps-sm-0{padding-left:0!important}.ps-sm-1{padding-left:.25rem!important}.ps-sm-2{padding-left:.5rem!important}.ps-sm-3{padding-left:1rem!important}.ps-sm-4{padding-left:1.5rem!important}.ps-sm-5{padding-left:3rem!important}.gap-sm-0{gap:0!important}.gap-sm-1{gap:.25rem!important}.gap-sm-2{gap:.5rem!important}.gap-sm-3{gap:1rem!important}.gap-sm-4{gap:1.5rem!important}.gap-sm-5{gap:3rem!important}.row-gap-sm-0{row-gap:0!important}.row-gap-sm-1{row-gap:.25rem!important}.row-gap-sm-2{row-gap:.5rem!important}.row-gap-sm-3{row-gap:1rem!important}.row-gap-sm-4{row-gap:1.5rem!important}.row-gap-sm-5{row-gap:3rem!important}.column-gap-sm-0{column-gap:0!important}.column-gap-sm-1{column-gap:.25rem!important}.column-gap-sm-2{column-gap:.5rem!important}.column-gap-sm-3{column-gap:1rem!important}.column-gap-sm-4{column-gap:1.5rem!important}.column-gap-sm-5{column-gap:3rem!important}.text-sm-start{text-align:left!important}.text-sm-end{text-align:right!important}.text-sm-center{text-align:center!important}}@media (width>=768px){.float-md-start{float:left!important}.float-md-end{float:right!important}.float-md-none{float:none!important}.object-fit-md-contain{object-fit:contain!important}.object-fit-md-cover{object-fit:cover!important}.object-fit-md-fill{object-fit:fill!important}.object-fit-md-scale{object-fit:scale-down!important}.object-fit-md-none{object-fit:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-grid{display:grid!important}.d-md-inline-grid{display:inline-grid!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}.d-md-none{display:none!important}.flex-md-fill{flex:auto!important}.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.justify-content-md-evenly{justify-content:space-evenly!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}.order-md-first{order:-1!important}.order-md-0{order:0!important}.order-md-1{order:1!important}.order-md-2{order:2!important}.order-md-3{order:3!important}.order-md-4{order:4!important}.order-md-5{order:5!important}.order-md-last{order:6!important}.m-md-0{margin:0!important}.m-md-1{margin:.25rem!important}.m-md-2{margin:.5rem!important}.m-md-3{margin:1rem!important}.m-md-4{margin:1.5rem!important}.m-md-5{margin:3rem!important}.m-md-auto{margin:auto!important}.mx-md-0{margin-left:0!important;margin-right:0!important}.mx-md-1{margin-left:.25rem!important;margin-right:.25rem!important}.mx-md-2{margin-left:.5rem!important;margin-right:.5rem!important}.mx-md-3{margin-left:1rem!important;margin-right:1rem!important}.mx-md-4{margin-left:1.5rem!important;margin-right:1.5rem!important}.mx-md-5{margin-left:3rem!important;margin-right:3rem!important}.mx-md-auto{margin-left:auto!important;margin-right:auto!important}.my-md-0{margin-top:0!important;margin-bottom:0!important}.my-md-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-md-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-md-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-md-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-md-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-md-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-md-0{margin-top:0!important}.mt-md-1{margin-top:.25rem!important}.mt-md-2{margin-top:.5rem!important}.mt-md-3{margin-top:1rem!important}.mt-md-4{margin-top:1.5rem!important}.mt-md-5{margin-top:3rem!important}.mt-md-auto{margin-top:auto!important}.me-md-0{margin-right:0!important}.me-md-1{margin-right:.25rem!important}.me-md-2{margin-right:.5rem!important}.me-md-3{margin-right:1rem!important}.me-md-4{margin-right:1.5rem!important}.me-md-5{margin-right:3rem!important}.me-md-auto{margin-right:auto!important}.mb-md-0{margin-bottom:0!important}.mb-md-1{margin-bottom:.25rem!important}.mb-md-2{margin-bottom:.5rem!important}.mb-md-3{margin-bottom:1rem!important}.mb-md-4{margin-bottom:1.5rem!important}.mb-md-5{margin-bottom:3rem!important}.mb-md-auto{margin-bottom:auto!important}.ms-md-0{margin-left:0!important}.ms-md-1{margin-left:.25rem!important}.ms-md-2{margin-left:.5rem!important}.ms-md-3{margin-left:1rem!important}.ms-md-4{margin-left:1.5rem!important}.ms-md-5{margin-left:3rem!important}.ms-md-auto{margin-left:auto!important}.p-md-0{padding:0!important}.p-md-1{padding:.25rem!important}.p-md-2{padding:.5rem!important}.p-md-3{padding:1rem!important}.p-md-4{padding:1.5rem!important}.p-md-5{padding:3rem!important}.px-md-0{padding-left:0!important;padding-right:0!important}.px-md-1{padding-left:.25rem!important;padding-right:.25rem!important}.px-md-2{padding-left:.5rem!important;padding-right:.5rem!important}.px-md-3{padding-left:1rem!important;padding-right:1rem!important}.px-md-4{padding-left:1.5rem!important;padding-right:1.5rem!important}.px-md-5{padding-left:3rem!important;padding-right:3rem!important}.py-md-0{padding-top:0!important;padding-bottom:0!important}.py-md-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-md-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-md-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-md-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-md-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-md-0{padding-top:0!important}.pt-md-1{padding-top:.25rem!important}.pt-md-2{padding-top:.5rem!important}.pt-md-3{padding-top:1rem!important}.pt-md-4{padding-top:1.5rem!important}.pt-md-5{padding-top:3rem!important}.pe-md-0{padding-right:0!important}.pe-md-1{padding-right:.25rem!important}.pe-md-2{padding-right:.5rem!important}.pe-md-3{padding-right:1rem!important}.pe-md-4{padding-right:1.5rem!important}.pe-md-5{padding-right:3rem!important}.pb-md-0{padding-bottom:0!important}.pb-md-1{padding-bottom:.25rem!important}.pb-md-2{padding-bottom:.5rem!important}.pb-md-3{padding-bottom:1rem!important}.pb-md-4{padding-bottom:1.5rem!important}.pb-md-5{padding-bottom:3rem!important}.ps-md-0{padding-left:0!important}.ps-md-1{padding-left:.25rem!important}.ps-md-2{padding-left:.5rem!important}.ps-md-3{padding-left:1rem!important}.ps-md-4{padding-left:1.5rem!important}.ps-md-5{padding-left:3rem!important}.gap-md-0{gap:0!important}.gap-md-1{gap:.25rem!important}.gap-md-2{gap:.5rem!important}.gap-md-3{gap:1rem!important}.gap-md-4{gap:1.5rem!important}.gap-md-5{gap:3rem!important}.row-gap-md-0{row-gap:0!important}.row-gap-md-1{row-gap:.25rem!important}.row-gap-md-2{row-gap:.5rem!important}.row-gap-md-3{row-gap:1rem!important}.row-gap-md-4{row-gap:1.5rem!important}.row-gap-md-5{row-gap:3rem!important}.column-gap-md-0{column-gap:0!important}.column-gap-md-1{column-gap:.25rem!important}.column-gap-md-2{column-gap:.5rem!important}.column-gap-md-3{column-gap:1rem!important}.column-gap-md-4{column-gap:1.5rem!important}.column-gap-md-5{column-gap:3rem!important}.text-md-start{text-align:left!important}.text-md-end{text-align:right!important}.text-md-center{text-align:center!important}}@media (width>=992px){.float-lg-start{float:left!important}.float-lg-end{float:right!important}.float-lg-none{float:none!important}.object-fit-lg-contain{object-fit:contain!important}.object-fit-lg-cover{object-fit:cover!important}.object-fit-lg-fill{object-fit:fill!important}.object-fit-lg-scale{object-fit:scale-down!important}.object-fit-lg-none{object-fit:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-grid{display:grid!important}.d-lg-inline-grid{display:inline-grid!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}.d-lg-none{display:none!important}.flex-lg-fill{flex:auto!important}.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.justify-content-lg-evenly{justify-content:space-evenly!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}.order-lg-first{order:-1!important}.order-lg-0{order:0!important}.order-lg-1{order:1!important}.order-lg-2{order:2!important}.order-lg-3{order:3!important}.order-lg-4{order:4!important}.order-lg-5{order:5!important}.order-lg-last{order:6!important}.m-lg-0{margin:0!important}.m-lg-1{margin:.25rem!important}.m-lg-2{margin:.5rem!important}.m-lg-3{margin:1rem!important}.m-lg-4{margin:1.5rem!important}.m-lg-5{margin:3rem!important}.m-lg-auto{margin:auto!important}.mx-lg-0{margin-left:0!important;margin-right:0!important}.mx-lg-1{margin-left:.25rem!important;margin-right:.25rem!important}.mx-lg-2{margin-left:.5rem!important;margin-right:.5rem!important}.mx-lg-3{margin-left:1rem!important;margin-right:1rem!important}.mx-lg-4{margin-left:1.5rem!important;margin-right:1.5rem!important}.mx-lg-5{margin-left:3rem!important;margin-right:3rem!important}.mx-lg-auto{margin-left:auto!important;margin-right:auto!important}.my-lg-0{margin-top:0!important;margin-bottom:0!important}.my-lg-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-lg-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-lg-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-lg-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-lg-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-lg-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-lg-0{margin-top:0!important}.mt-lg-1{margin-top:.25rem!important}.mt-lg-2{margin-top:.5rem!important}.mt-lg-3{margin-top:1rem!important}.mt-lg-4{margin-top:1.5rem!important}.mt-lg-5{margin-top:3rem!important}.mt-lg-auto{margin-top:auto!important}.me-lg-0{margin-right:0!important}.me-lg-1{margin-right:.25rem!important}.me-lg-2{margin-right:.5rem!important}.me-lg-3{margin-right:1rem!important}.me-lg-4{margin-right:1.5rem!important}.me-lg-5{margin-right:3rem!important}.me-lg-auto{margin-right:auto!important}.mb-lg-0{margin-bottom:0!important}.mb-lg-1{margin-bottom:.25rem!important}.mb-lg-2{margin-bottom:.5rem!important}.mb-lg-3{margin-bottom:1rem!important}.mb-lg-4{margin-bottom:1.5rem!important}.mb-lg-5{margin-bottom:3rem!important}.mb-lg-auto{margin-bottom:auto!important}.ms-lg-0{margin-left:0!important}.ms-lg-1{margin-left:.25rem!important}.ms-lg-2{margin-left:.5rem!important}.ms-lg-3{margin-left:1rem!important}.ms-lg-4{margin-left:1.5rem!important}.ms-lg-5{margin-left:3rem!important}.ms-lg-auto{margin-left:auto!important}.p-lg-0{padding:0!important}.p-lg-1{padding:.25rem!important}.p-lg-2{padding:.5rem!important}.p-lg-3{padding:1rem!important}.p-lg-4{padding:1.5rem!important}.p-lg-5{padding:3rem!important}.px-lg-0{padding-left:0!important;padding-right:0!important}.px-lg-1{padding-left:.25rem!important;padding-right:.25rem!important}.px-lg-2{padding-left:.5rem!important;padding-right:.5rem!important}.px-lg-3{padding-left:1rem!important;padding-right:1rem!important}.px-lg-4{padding-left:1.5rem!important;padding-right:1.5rem!important}.px-lg-5{padding-left:3rem!important;padding-right:3rem!important}.py-lg-0{padding-top:0!important;padding-bottom:0!important}.py-lg-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-lg-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-lg-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-lg-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-lg-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-lg-0{padding-top:0!important}.pt-lg-1{padding-top:.25rem!important}.pt-lg-2{padding-top:.5rem!important}.pt-lg-3{padding-top:1rem!important}.pt-lg-4{padding-top:1.5rem!important}.pt-lg-5{padding-top:3rem!important}.pe-lg-0{padding-right:0!important}.pe-lg-1{padding-right:.25rem!important}.pe-lg-2{padding-right:.5rem!important}.pe-lg-3{padding-right:1rem!important}.pe-lg-4{padding-right:1.5rem!important}.pe-lg-5{padding-right:3rem!important}.pb-lg-0{padding-bottom:0!important}.pb-lg-1{padding-bottom:.25rem!important}.pb-lg-2{padding-bottom:.5rem!important}.pb-lg-3{padding-bottom:1rem!important}.pb-lg-4{padding-bottom:1.5rem!important}.pb-lg-5{padding-bottom:3rem!important}.ps-lg-0{padding-left:0!important}.ps-lg-1{padding-left:.25rem!important}.ps-lg-2{padding-left:.5rem!important}.ps-lg-3{padding-left:1rem!important}.ps-lg-4{padding-left:1.5rem!important}.ps-lg-5{padding-left:3rem!important}.gap-lg-0{gap:0!important}.gap-lg-1{gap:.25rem!important}.gap-lg-2{gap:.5rem!important}.gap-lg-3{gap:1rem!important}.gap-lg-4{gap:1.5rem!important}.gap-lg-5{gap:3rem!important}.row-gap-lg-0{row-gap:0!important}.row-gap-lg-1{row-gap:.25rem!important}.row-gap-lg-2{row-gap:.5rem!important}.row-gap-lg-3{row-gap:1rem!important}.row-gap-lg-4{row-gap:1.5rem!important}.row-gap-lg-5{row-gap:3rem!important}.column-gap-lg-0{column-gap:0!important}.column-gap-lg-1{column-gap:.25rem!important}.column-gap-lg-2{column-gap:.5rem!important}.column-gap-lg-3{column-gap:1rem!important}.column-gap-lg-4{column-gap:1.5rem!important}.column-gap-lg-5{column-gap:3rem!important}.text-lg-start{text-align:left!important}.text-lg-end{text-align:right!important}.text-lg-center{text-align:center!important}}@media (width>=1200px){.float-xl-start{float:left!important}.float-xl-end{float:right!important}.float-xl-none{float:none!important}.object-fit-xl-contain{object-fit:contain!important}.object-fit-xl-cover{object-fit:cover!important}.object-fit-xl-fill{object-fit:fill!important}.object-fit-xl-scale{object-fit:scale-down!important}.object-fit-xl-none{object-fit:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-grid{display:grid!important}.d-xl-inline-grid{display:inline-grid!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}.d-xl-none{display:none!important}.flex-xl-fill{flex:auto!important}.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.justify-content-xl-evenly{justify-content:space-evenly!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}.order-xl-first{order:-1!important}.order-xl-0{order:0!important}.order-xl-1{order:1!important}.order-xl-2{order:2!important}.order-xl-3{order:3!important}.order-xl-4{order:4!important}.order-xl-5{order:5!important}.order-xl-last{order:6!important}.m-xl-0{margin:0!important}.m-xl-1{margin:.25rem!important}.m-xl-2{margin:.5rem!important}.m-xl-3{margin:1rem!important}.m-xl-4{margin:1.5rem!important}.m-xl-5{margin:3rem!important}.m-xl-auto{margin:auto!important}.mx-xl-0{margin-left:0!important;margin-right:0!important}.mx-xl-1{margin-left:.25rem!important;margin-right:.25rem!important}.mx-xl-2{margin-left:.5rem!important;margin-right:.5rem!important}.mx-xl-3{margin-left:1rem!important;margin-right:1rem!important}.mx-xl-4{margin-left:1.5rem!important;margin-right:1.5rem!important}.mx-xl-5{margin-left:3rem!important;margin-right:3rem!important}.mx-xl-auto{margin-left:auto!important;margin-right:auto!important}.my-xl-0{margin-top:0!important;margin-bottom:0!important}.my-xl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xl-0{margin-top:0!important}.mt-xl-1{margin-top:.25rem!important}.mt-xl-2{margin-top:.5rem!important}.mt-xl-3{margin-top:1rem!important}.mt-xl-4{margin-top:1.5rem!important}.mt-xl-5{margin-top:3rem!important}.mt-xl-auto{margin-top:auto!important}.me-xl-0{margin-right:0!important}.me-xl-1{margin-right:.25rem!important}.me-xl-2{margin-right:.5rem!important}.me-xl-3{margin-right:1rem!important}.me-xl-4{margin-right:1.5rem!important}.me-xl-5{margin-right:3rem!important}.me-xl-auto{margin-right:auto!important}.mb-xl-0{margin-bottom:0!important}.mb-xl-1{margin-bottom:.25rem!important}.mb-xl-2{margin-bottom:.5rem!important}.mb-xl-3{margin-bottom:1rem!important}.mb-xl-4{margin-bottom:1.5rem!important}.mb-xl-5{margin-bottom:3rem!important}.mb-xl-auto{margin-bottom:auto!important}.ms-xl-0{margin-left:0!important}.ms-xl-1{margin-left:.25rem!important}.ms-xl-2{margin-left:.5rem!important}.ms-xl-3{margin-left:1rem!important}.ms-xl-4{margin-left:1.5rem!important}.ms-xl-5{margin-left:3rem!important}.ms-xl-auto{margin-left:auto!important}.p-xl-0{padding:0!important}.p-xl-1{padding:.25rem!important}.p-xl-2{padding:.5rem!important}.p-xl-3{padding:1rem!important}.p-xl-4{padding:1.5rem!important}.p-xl-5{padding:3rem!important}.px-xl-0{padding-left:0!important;padding-right:0!important}.px-xl-1{padding-left:.25rem!important;padding-right:.25rem!important}.px-xl-2{padding-left:.5rem!important;padding-right:.5rem!important}.px-xl-3{padding-left:1rem!important;padding-right:1rem!important}.px-xl-4{padding-left:1.5rem!important;padding-right:1.5rem!important}.px-xl-5{padding-left:3rem!important;padding-right:3rem!important}.py-xl-0{padding-top:0!important;padding-bottom:0!important}.py-xl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xl-0{padding-top:0!important}.pt-xl-1{padding-top:.25rem!important}.pt-xl-2{padding-top:.5rem!important}.pt-xl-3{padding-top:1rem!important}.pt-xl-4{padding-top:1.5rem!important}.pt-xl-5{padding-top:3rem!important}.pe-xl-0{padding-right:0!important}.pe-xl-1{padding-right:.25rem!important}.pe-xl-2{padding-right:.5rem!important}.pe-xl-3{padding-right:1rem!important}.pe-xl-4{padding-right:1.5rem!important}.pe-xl-5{padding-right:3rem!important}.pb-xl-0{padding-bottom:0!important}.pb-xl-1{padding-bottom:.25rem!important}.pb-xl-2{padding-bottom:.5rem!important}.pb-xl-3{padding-bottom:1rem!important}.pb-xl-4{padding-bottom:1.5rem!important}.pb-xl-5{padding-bottom:3rem!important}.ps-xl-0{padding-left:0!important}.ps-xl-1{padding-left:.25rem!important}.ps-xl-2{padding-left:.5rem!important}.ps-xl-3{padding-left:1rem!important}.ps-xl-4{padding-left:1.5rem!important}.ps-xl-5{padding-left:3rem!important}.gap-xl-0{gap:0!important}.gap-xl-1{gap:.25rem!important}.gap-xl-2{gap:.5rem!important}.gap-xl-3{gap:1rem!important}.gap-xl-4{gap:1.5rem!important}.gap-xl-5{gap:3rem!important}.row-gap-xl-0{row-gap:0!important}.row-gap-xl-1{row-gap:.25rem!important}.row-gap-xl-2{row-gap:.5rem!important}.row-gap-xl-3{row-gap:1rem!important}.row-gap-xl-4{row-gap:1.5rem!important}.row-gap-xl-5{row-gap:3rem!important}.column-gap-xl-0{column-gap:0!important}.column-gap-xl-1{column-gap:.25rem!important}.column-gap-xl-2{column-gap:.5rem!important}.column-gap-xl-3{column-gap:1rem!important}.column-gap-xl-4{column-gap:1.5rem!important}.column-gap-xl-5{column-gap:3rem!important}.text-xl-start{text-align:left!important}.text-xl-end{text-align:right!important}.text-xl-center{text-align:center!important}}@media (width>=1400px){.float-xxl-start{float:left!important}.float-xxl-end{float:right!important}.float-xxl-none{float:none!important}.object-fit-xxl-contain{object-fit:contain!important}.object-fit-xxl-cover{object-fit:cover!important}.object-fit-xxl-fill{object-fit:fill!important}.object-fit-xxl-scale{object-fit:scale-down!important}.object-fit-xxl-none{object-fit:none!important}.d-xxl-inline{display:inline!important}.d-xxl-inline-block{display:inline-block!important}.d-xxl-block{display:block!important}.d-xxl-grid{display:grid!important}.d-xxl-inline-grid{display:inline-grid!important}.d-xxl-table{display:table!important}.d-xxl-table-row{display:table-row!important}.d-xxl-table-cell{display:table-cell!important}.d-xxl-flex{display:flex!important}.d-xxl-inline-flex{display:inline-flex!important}.d-xxl-none{display:none!important}.flex-xxl-fill{flex:auto!important}.flex-xxl-row{flex-direction:row!important}.flex-xxl-column{flex-direction:column!important}.flex-xxl-row-reverse{flex-direction:row-reverse!important}.flex-xxl-column-reverse{flex-direction:column-reverse!important}.flex-xxl-grow-0{flex-grow:0!important}.flex-xxl-grow-1{flex-grow:1!important}.flex-xxl-shrink-0{flex-shrink:0!important}.flex-xxl-shrink-1{flex-shrink:1!important}.flex-xxl-wrap{flex-wrap:wrap!important}.flex-xxl-nowrap{flex-wrap:nowrap!important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-xxl-start{justify-content:flex-start!important}.justify-content-xxl-end{justify-content:flex-end!important}.justify-content-xxl-center{justify-content:center!important}.justify-content-xxl-between{justify-content:space-between!important}.justify-content-xxl-around{justify-content:space-around!important}.justify-content-xxl-evenly{justify-content:space-evenly!important}.align-items-xxl-start{align-items:flex-start!important}.align-items-xxl-end{align-items:flex-end!important}.align-items-xxl-center{align-items:center!important}.align-items-xxl-baseline{align-items:baseline!important}.align-items-xxl-stretch{align-items:stretch!important}.align-content-xxl-start{align-content:flex-start!important}.align-content-xxl-end{align-content:flex-end!important}.align-content-xxl-center{align-content:center!important}.align-content-xxl-between{align-content:space-between!important}.align-content-xxl-around{align-content:space-around!important}.align-content-xxl-stretch{align-content:stretch!important}.align-self-xxl-auto{align-self:auto!important}.align-self-xxl-start{align-self:flex-start!important}.align-self-xxl-end{align-self:flex-end!important}.align-self-xxl-center{align-self:center!important}.align-self-xxl-baseline{align-self:baseline!important}.align-self-xxl-stretch{align-self:stretch!important}.order-xxl-first{order:-1!important}.order-xxl-0{order:0!important}.order-xxl-1{order:1!important}.order-xxl-2{order:2!important}.order-xxl-3{order:3!important}.order-xxl-4{order:4!important}.order-xxl-5{order:5!important}.order-xxl-last{order:6!important}.m-xxl-0{margin:0!important}.m-xxl-1{margin:.25rem!important}.m-xxl-2{margin:.5rem!important}.m-xxl-3{margin:1rem!important}.m-xxl-4{margin:1.5rem!important}.m-xxl-5{margin:3rem!important}.m-xxl-auto{margin:auto!important}.mx-xxl-0{margin-left:0!important;margin-right:0!important}.mx-xxl-1{margin-left:.25rem!important;margin-right:.25rem!important}.mx-xxl-2{margin-left:.5rem!important;margin-right:.5rem!important}.mx-xxl-3{margin-left:1rem!important;margin-right:1rem!important}.mx-xxl-4{margin-left:1.5rem!important;margin-right:1.5rem!important}.mx-xxl-5{margin-left:3rem!important;margin-right:3rem!important}.mx-xxl-auto{margin-left:auto!important;margin-right:auto!important}.my-xxl-0{margin-top:0!important;margin-bottom:0!important}.my-xxl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xxl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xxl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xxl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xxl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xxl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xxl-0{margin-top:0!important}.mt-xxl-1{margin-top:.25rem!important}.mt-xxl-2{margin-top:.5rem!important}.mt-xxl-3{margin-top:1rem!important}.mt-xxl-4{margin-top:1.5rem!important}.mt-xxl-5{margin-top:3rem!important}.mt-xxl-auto{margin-top:auto!important}.me-xxl-0{margin-right:0!important}.me-xxl-1{margin-right:.25rem!important}.me-xxl-2{margin-right:.5rem!important}.me-xxl-3{margin-right:1rem!important}.me-xxl-4{margin-right:1.5rem!important}.me-xxl-5{margin-right:3rem!important}.me-xxl-auto{margin-right:auto!important}.mb-xxl-0{margin-bottom:0!important}.mb-xxl-1{margin-bottom:.25rem!important}.mb-xxl-2{margin-bottom:.5rem!important}.mb-xxl-3{margin-bottom:1rem!important}.mb-xxl-4{margin-bottom:1.5rem!important}.mb-xxl-5{margin-bottom:3rem!important}.mb-xxl-auto{margin-bottom:auto!important}.ms-xxl-0{margin-left:0!important}.ms-xxl-1{margin-left:.25rem!important}.ms-xxl-2{margin-left:.5rem!important}.ms-xxl-3{margin-left:1rem!important}.ms-xxl-4{margin-left:1.5rem!important}.ms-xxl-5{margin-left:3rem!important}.ms-xxl-auto{margin-left:auto!important}.p-xxl-0{padding:0!important}.p-xxl-1{padding:.25rem!important}.p-xxl-2{padding:.5rem!important}.p-xxl-3{padding:1rem!important}.p-xxl-4{padding:1.5rem!important}.p-xxl-5{padding:3rem!important}.px-xxl-0{padding-left:0!important;padding-right:0!important}.px-xxl-1{padding-left:.25rem!important;padding-right:.25rem!important}.px-xxl-2{padding-left:.5rem!important;padding-right:.5rem!important}.px-xxl-3{padding-left:1rem!important;padding-right:1rem!important}.px-xxl-4{padding-left:1.5rem!important;padding-right:1.5rem!important}.px-xxl-5{padding-left:3rem!important;padding-right:3rem!important}.py-xxl-0{padding-top:0!important;padding-bottom:0!important}.py-xxl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xxl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xxl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xxl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xxl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xxl-0{padding-top:0!important}.pt-xxl-1{padding-top:.25rem!important}.pt-xxl-2{padding-top:.5rem!important}.pt-xxl-3{padding-top:1rem!important}.pt-xxl-4{padding-top:1.5rem!important}.pt-xxl-5{padding-top:3rem!important}.pe-xxl-0{padding-right:0!important}.pe-xxl-1{padding-right:.25rem!important}.pe-xxl-2{padding-right:.5rem!important}.pe-xxl-3{padding-right:1rem!important}.pe-xxl-4{padding-right:1.5rem!important}.pe-xxl-5{padding-right:3rem!important}.pb-xxl-0{padding-bottom:0!important}.pb-xxl-1{padding-bottom:.25rem!important}.pb-xxl-2{padding-bottom:.5rem!important}.pb-xxl-3{padding-bottom:1rem!important}.pb-xxl-4{padding-bottom:1.5rem!important}.pb-xxl-5{padding-bottom:3rem!important}.ps-xxl-0{padding-left:0!important}.ps-xxl-1{padding-left:.25rem!important}.ps-xxl-2{padding-left:.5rem!important}.ps-xxl-3{padding-left:1rem!important}.ps-xxl-4{padding-left:1.5rem!important}.ps-xxl-5{padding-left:3rem!important}.gap-xxl-0{gap:0!important}.gap-xxl-1{gap:.25rem!important}.gap-xxl-2{gap:.5rem!important}.gap-xxl-3{gap:1rem!important}.gap-xxl-4{gap:1.5rem!important}.gap-xxl-5{gap:3rem!important}.row-gap-xxl-0{row-gap:0!important}.row-gap-xxl-1{row-gap:.25rem!important}.row-gap-xxl-2{row-gap:.5rem!important}.row-gap-xxl-3{row-gap:1rem!important}.row-gap-xxl-4{row-gap:1.5rem!important}.row-gap-xxl-5{row-gap:3rem!important}.column-gap-xxl-0{column-gap:0!important}.column-gap-xxl-1{column-gap:.25rem!important}.column-gap-xxl-2{column-gap:.5rem!important}.column-gap-xxl-3{column-gap:1rem!important}.column-gap-xxl-4{column-gap:1.5rem!important}.column-gap-xxl-5{column-gap:3rem!important}.text-xxl-start{text-align:left!important}.text-xxl-end{text-align:right!important}.text-xxl-center{text-align:center!important}}@media (width>=1200px){.fs-1{font-size:2.5rem!important}.fs-2{font-size:2rem!important}.fs-3{font-size:1.75rem!important}.fs-4{font-size:1.5rem!important}}@media print{.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-grid{display:grid!important}.d-print-inline-grid{display:inline-grid!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}.d-print-none{display:none!important}}html[data-theme=light]{--bg:#fbf9f4;--surface:#fff;--surface-2:#f8f6f2;--border:#e3ddcf;--border-soft:#ebe6da;--text:#0b0b0b;--text-muted:#5b564d;--text-dim:#8a8478;--brand-red:#c71f38;--amber:#c71f38;--teal:#a33c4b;--periwinkle:#4a463e;--rose:#c97c89;--sage:#8c1729;--amber-dim:#c71f381f;--teal-dim:#a33c4b1f;--nav-bg:#fbf9f4db;--on-accent:#fbf6f0;--state-active:#2e7d64;--state-active-dim:#2e7d641f;--state-maintenance:#96620a;--state-maintenance-dim:#96620a1f;--state-security:#c71f38;--state-security-dim:#c71f381f;--state-archived:#6b665c;--state-archived-dim:#6b665c1a}html[data-theme=dark]{--bg:#0b0b0b;--surface:#161616;--surface-2:#1e1e1e;--border:#2c2c2c;--border-soft:#232323;--text:#f1eee8;--text-muted:#9a948d;--text-dim:#66605a;--brand-red:#c71f38;--amber:#da3550;--teal:#e8637a;--periwinkle:#5c5854;--rose:#f0a9b6;--sage:#8c1729;--amber-dim:#da355029;--teal-dim:#e8637a29;--nav-bg:#0b0b0bdb;--state-active:#58b79a;--state-active-dim:#58b79a29;--state-maintenance:#dca23c;--state-maintenance-dim:#dca23c29;--state-security:#e8637a;--state-security-dim:#e8637a29;--state-archived:#8a847c;--state-archived-dim:#8a847c24}html{background:var(--bg);scroll-behavior:smooth;--maxw:1180px;--radius:12px;--mono:"Space Grotesk", sans-serif;--sans:"Barlow", -apple-system, sans-serif;transition:background .2s}body{background:var(--bg);color:var(--text);font-family:var(--sans);-webkit-font-smoothing:antialiased;margin:0;font-size:16px;line-height:1.6;transition:background .2s,color .2s}*{box-sizing:border-box;transition:background-color .2s,border-color .2s,color .2s}a{color:inherit;text-decoration:none}img{max-width:100%;display:block}.wrap{max-width:var(--maxw);margin:0 auto;padding:0}.eyebrow{font-family:var(--mono);letter-spacing:.14em;text-transform:uppercase;color:var(--teal);align-items:center;gap:8px;font-size:12px;display:inline-flex}.eyebrow:before{content:"";background:var(--teal);width:6px;height:6px;box-shadow:0 0 0 3px var(--teal-dim);border-radius:50%}h1,h2,h3,h4{font-family:var(--mono);color:var(--text);letter-spacing:-.01em;margin:0;font-weight:600}p{color:var(--text-muted);margin:0}section{padding:88px 0}.section-head{max-width:640px;margin-bottom:48px}.section-head h2{margin-top:14px;font-size:30px}.section-head p{margin-top:14px;font-size:16.5px}.core-band{background:var(--surface-2);border:1px solid var(--border-soft);border-left:3px solid var(--teal);border-radius:var(--radius);margin-bottom:40px;margin-left:0;margin-right:0;padding:32px 40px}@media (width<=991.98px){.core-band .border-start{margin-top:16px;border:none!important;padding-left:0!important}}.core-band-label{font-family:var(--mono);letter-spacing:.14em;text-transform:uppercase;color:var(--teal);align-items:center;gap:8px;font-size:12px;display:inline-flex;margin-bottom:14px!important}.core-band-label:before{content:"";background:var(--teal);width:6px;height:6px;box-shadow:0 0 0 3px var(--teal-dim);border-radius:50%}.core-band code{font-family:var(--mono);color:var(--teal);background:var(--teal-dim);border:1px solid var(--teal);border-radius:6px;margin:0 8px 10px 0;padding:5px 12px;font-size:13px;display:inline-block}.core-band p{margin:0;font-size:15.5px;line-height:1.75}.tag-badge{font-family:var(--mono);color:var(--teal);background:var(--teal-dim);border:1px solid var(--teal);border-radius:6px;padding:5px 12px;font-size:13px;text-decoration:none;display:inline-block}.tag-badge:hover{background:var(--teal);color:var(--on-accent)}header{z-index:50;background:var(--nav-bg);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border-bottom:1px solid var(--border-soft);position:sticky;top:0}.logo{align-items:center;display:flex}.logo-full{flex-shrink:0;width:auto;height:26px}.navbar{min-height:72px;padding-top:0;padding-bottom:0}.navbar-nav .nav-link{color:var(--text-muted);white-space:nowrap;font-size:14.5px;transition:color .15s}.navbar-nav .nav-link:hover{color:var(--text)}@media (width>=992px){.navbar-nav{gap:30px}.navbar-nav .nav-link{padding-left:0;padding-right:0}}.nav-cta{font-family:var(--mono);border:1px solid var(--amber);border-radius:999px;font-size:13.5px;font-weight:500;transition:background .15s,color .15s;color:var(--amber)!important;padding:9px 18px!important}.nav-cta:hover{background:var(--amber);color:var(--on-accent)!important}.navbar-toggler{border:none;padding:8px}.navbar-toggler:focus{box-shadow:none}html[data-theme=light] .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba(11,11,11,1)' stroke-width='2' stroke-linecap='round' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}html[data-theme=dark] .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba(241,238,232,1)' stroke-width='2' stroke-linecap='round' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}@media (width<=991.98px){:is(header:has(.navbar-collapse.show),header:has(.navbar-collapse.collapsing)){background:var(--bg);-webkit-backdrop-filter:none;backdrop-filter:none}.navbar-collapse{background:var(--bg);border-top:1px solid var(--border-soft);margin:0 -28px;padding:0 28px}.navbar-nav{padding:10px 0 20px}.navbar-nav .nav-link{border-bottom:1px solid var(--border-soft);padding:14px 0;font-size:16px}.navbar-nav .nav-cta{text-align:center;border:1px solid var(--amber);border-radius:999px;width:100%;margin-top:16px;padding:12px 18px;font-size:14.5px;transition:background .15s,color .15s;display:block;color:var(--amber)!important}}.theme-toggle{border:1px solid var(--border);width:38px;height:38px;color:var(--text-muted);cursor:pointer;background:0 0;border-radius:9px;flex-shrink:0;justify-content:center;align-items:center;transition:border-color .15s,color .15s;display:flex}.theme-toggle:hover{border-color:var(--text-muted);color:var(--text)}.theme-toggle svg{width:18px;height:18px}.theme-toggle .icon-moon,html[data-theme=light] .theme-toggle .icon-sun{display:none}html[data-theme=light] .theme-toggle .icon-moon{display:block}.btn{font-family:var(--mono);border:1px solid var(--border);white-space:nowrap;border-radius:8px;align-items:center;gap:8px;padding:11px 20px;font-size:13.5px;font-weight:500;transition:border-color .15s,background .15s,transform .15s;display:inline-flex}.btn:hover{transform:translateY(-1px)}.btn svg{width:15px;height:15px}.btn-primary{background:var(--amber);color:var(--on-accent);border-color:var(--amber);font-weight:600}.btn-primary:hover{background:var(--amber);box-shadow:0 6px 20px var(--amber-dim)}.btn-ghost{color:var(--text)}.btn-ghost:hover{border-color:var(--text-muted)}.btn-sm{border-radius:7px;padding:8px 14px;font-size:12.5px}.hero{padding:76px 0 40px}.hero h1{letter-spacing:-.02em;margin-top:18px;font-size:44px;line-height:1.15}.hero p.sub{max-width:520px;color:var(--text-muted);margin-top:20px;font-size:18px}.hero-grid{grid-template-columns:1.05fr .95fr;align-items:center;gap:64px;display:grid}.hero-ctas{flex-wrap:wrap;gap:14px;margin-top:32px;display:flex}.hero-stack-note{font-family:var(--mono);color:var(--text-dim);flex-wrap:wrap;gap:18px;margin-top:34px;font-size:12.5px;display:flex}.hero-stack-note span{color:var(--text-muted)}.hero-visual{flex-direction:column;gap:18px;display:flex}.hero-art{background:var(--surface);border:1px solid var(--border);color:var(--text-dim);border-radius:16px;margin:0;padding:22px 24px 18px;position:relative}.hero-art figcaption{font-family:var(--mono);letter-spacing:.12em;text-transform:uppercase;color:var(--text-dim);margin-bottom:14px;font-size:11px}.hero-art svg{stroke:currentColor;fill:none;stroke-width:1.25px;stroke-linecap:round;stroke-linejoin:round;width:100%;height:auto;display:block;overflow:visible}.hero-art .art-accent{stroke:var(--teal)}.hero-art .art-hot{stroke:var(--brand-red)}.hero-art .art-fill{fill:var(--teal-dim);stroke:var(--teal)}.hero-art .art-dot{fill:var(--teal);stroke:none}.hero-art .art-dash{stroke-dasharray:4 4;opacity:.75}.hero-art .art-faint{opacity:.45}.pipeline{background:var(--surface);border:1px solid var(--border);border-radius:16px;padding:26px 26px 30px;position:relative}.pipeline-label{font-family:var(--mono);letter-spacing:.12em;text-transform:uppercase;color:var(--text-dim);justify-content:space-between;margin-bottom:18px;font-size:11px;display:flex}.pipeline-track{padding-left:28px;position:relative}.pipeline-track:before{content:"";background:linear-gradient(180deg, var(--sage), var(--brand-red));opacity:.5;width:2px;position:absolute;top:10px;bottom:10px;left:9px}.pl-step{background:var(--surface-2);border:1px solid var(--border-soft);border-radius:9px;justify-content:space-between;align-items:center;gap:10px;margin-bottom:10px;padding:13px 16px;display:flex;position:relative}.pl-step:last-child{margin-bottom:0}.pl-step:before{content:"";background:var(--bg);border:2px solid var(--teal);border-radius:50%;width:9px;height:9px;position:absolute;top:50%;left:-23px;transform:translateY(-50%)}.pl-step .name{font-family:var(--mono);color:var(--text);font-size:13.5px;font-weight:500}.pl-step .tag{font-family:var(--mono);color:var(--text-dim);font-size:10.5px}.pl-packet{background:var(--brand-red);border-radius:50%;width:14px;height:14px;animation:5s ease-in-out infinite travel;position:absolute;left:2px;box-shadow:0 0 12px 2px #c71f3838}@keyframes travel{0%{opacity:0;top:8px}6%{opacity:1}46%{opacity:1;top:calc(100% - 22px)}50%{opacity:0}50.01%{top:8px}56%{opacity:1}96%{opacity:1;top:calc(100% - 22px)}to{opacity:0;top:calc(100% - 22px)}}@media (prefers-reduced-motion:reduce){.pl-packet{opacity:1;animation:none;top:8px}html{scroll-behavior:auto}}.split{grid-template-columns:1.1fr .9fr;align-items:start;gap:64px;display:grid}.split h2{font-size:28px}.split .lede{color:var(--text-muted);margin-top:16px;font-size:17px;line-height:1.7}.extending{font-family:var(--mono);letter-spacing:.1em;text-transform:uppercase;color:var(--text-dim);align-items:center;gap:12px;margin-top:26px;font-size:12px;display:flex}.extending .extending-logo{text-transform:none;align-items:center;display:flex}.extending .extending-logo img{width:auto;height:36px;display:block}.extending .lockup{font-family:var(--mono);color:var(--text);text-transform:none;letter-spacing:0;font-size:15px;font-weight:600}.extending .lockup .x{color:var(--text-dim);margin:0 6px;font-weight:400}.traits{grid-template-columns:1fr 1fr;gap:14px;display:grid}.trait{background:var(--surface);border:1px solid var(--border);text-align:left;border-radius:12px;padding:22px 18px}.trait .icon{margin-bottom:12px}.trait p{font-family:var(--mono);color:var(--text);font-size:13px;font-weight:500;line-height:1.4}.products-grid{grid-template-columns:repeat(3,1fr);gap:18px;display:grid}.card{background:var(--surface);border:1px solid var(--border);border-top:3px solid var(--accent,var(--teal));border-radius:var(--radius);flex-direction:column;height:100%;padding:26px 24px 24px;display:flex}.card .card-top{justify-content:space-between;align-items:flex-start;margin-bottom:14px;display:flex}.card h3{color:var(--text);font-size:20px}.card .kicker{font-family:var(--mono);color:var(--text-dim);letter-spacing:.1em;text-transform:uppercase;margin-bottom:6px;font-size:10.5px;display:block}.card p.lede{color:var(--text);margin-bottom:10px;font-size:15px;font-weight:500;line-height:1.5}.card p.desc{flex-grow:1;font-size:14.5px;line-height:1.65}.card .card-btns{flex-wrap:wrap;gap:8px;margin-top:20px;display:flex}.badges{flex-wrap:wrap;gap:5px;margin-bottom:16px;display:flex}.badges img{border-radius:3px;height:19px}.packages-grid{grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:18px;display:grid}.packages-divider{border:none;border-top:1px solid var(--border);grid-column:1/-1;margin:4px 0}.package-card{border-top-width:4px;transition:box-shadow .2s,transform .2s}.package-card:hover{box-shadow:0 6px 20px var(--accent-dim);transform:translateY(-2px)}.package-card .card-top{margin-bottom:12px}.package-card h3{margin:0}.package-state{border:1px solid var(--accent);background:var(--accent-dim);color:var(--accent);font-family:var(--mono);letter-spacing:.1em;text-transform:uppercase;border-radius:999px;align-items:center;gap:7px;margin-bottom:8px;padding:4px 10px;font-size:10.5px;font-weight:600;display:inline-flex}.package-state:before{content:"";background:var(--accent);border-radius:50%;flex:none;width:7px;height:7px}.package-card--active{--accent:var(--state-active);--accent-dim:var(--state-active-dim)}.package-card--maintenance{--accent:var(--state-maintenance);--accent-dim:var(--state-maintenance-dim)}.package-card--maintenance .package-state:before{box-shadow:inset 0 0 0 2px var(--accent);background:0 0}.package-card--security-only{--accent:var(--state-security);--accent-dim:var(--state-security-dim)}.package-card--security-only .package-state:before{box-shadow:0 0 0 3px var(--accent-dim)}.package-card--archived,.package-card--unknown{--accent:var(--state-archived);--accent-dim:var(--state-archived-dim);border-style:solid dashed dashed}.package-card--archived h3,.package-card--unknown h3{color:var(--text-muted)}.package-card--archived .package-state:before,.package-card--unknown .package-state:before{box-shadow:inset 0 0 0 2px var(--accent);background:0 0;border-radius:2px}.package-card--archived .badges img,.package-card--unknown .badges img{opacity:.65}.package-description{color:var(--text-muted);margin:0 0 14px;font-size:14px}.package-archived{color:var(--text-dim);margin:0;font-size:12px}.packages-generated-at{color:var(--text-dim);margin-top:22px}@media (prefers-reduced-motion:reduce){.package-card{transition:none}.package-card:hover{transform:none}}.standards-grid{background:var(--border);border:1px solid var(--border);border-radius:var(--radius);grid-template-columns:repeat(3,1fr);gap:1px;display:grid;overflow:hidden}.standard{background:var(--surface);padding:30px 26px}.standard h4{color:var(--text);margin-bottom:10px;font-size:16px}.standard h4 a{color:inherit;border-bottom:1px solid var(--border)}.standard h4 a:hover{color:var(--amber);border-color:var(--amber)}.standard p{font-size:14px;line-height:1.65}.standard .get-started{font-family:var(--mono);color:var(--text-dim);margin-top:14px;font-size:12px}.standard .get-started a{color:var(--teal)}.standard .get-started a:hover{text-decoration:underline}.components-grid{grid-template-columns:1fr 1fr;gap:0 40px;display:grid}details.acc{border-bottom:1px solid var(--border-soft)}details.acc summary{cursor:pointer;font-family:var(--mono);color:var(--text);justify-content:space-between;align-items:center;padding:18px 0;font-size:15px;font-weight:500;list-style:none;display:flex}details.acc summary::-webkit-details-marker{display:none}details.acc summary .chev{color:var(--text-dim);font-size:18px;transition:transform .2s}details.acc[open] summary .chev{color:var(--amber);transform:rotate(45deg)}.acc-body{padding:0 0 20px}.pkg-row{border-top:1px solid var(--border-soft);justify-content:space-between;gap:16px;padding:9px 0;display:flex}.pkg-row:first-child{border-top:none}.pkg-row a{font-family:var(--mono);color:var(--teal);white-space:nowrap;font-size:13.5px}.pkg-row a:hover{text-decoration:underline}.pkg-row .pkg-desc{color:var(--text-muted);text-align:right;font-size:13px}.posts-grid{grid-template-columns:repeat(3,1fr);gap:20px;display:grid}.posts-grid-2-col{grid-template-columns:repeat(2,1fr);gap:20px;display:grid}.post-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:24px;transition:border-color .15s}.post-card:hover{border-color:var(--text-dim)}.post-card h4{color:var(--text);margin-top:10px;font-size:16.5px;line-height:1.4}.post-card p{margin-top:10px;font-size:13.5px;line-height:1.6}.post-card .read-more{font-family:var(--mono);color:var(--amber);margin-top:14px;margin-left:2px;font-size:12px;display:inline-block}.post-date{font-family:var(--mono);color:var(--text-dim);letter-spacing:.06em;text-transform:uppercase;font-size:11.5px}.about-band{background:var(--surface);border-top:1px solid var(--border-soft);border-bottom:1px solid var(--border-soft)}.about-inner{grid-template-columns:1fr 1.3fr;align-items:center;gap:56px;display:grid}.about-inner h2{margin-top:14px;font-size:26px;line-height:1.3}.about-inner .about-text{font-size:15px;line-height:1.75}.about-inner .about-cta{margin-top:22px}footer{padding:50px 0 40px}.footer-top{border-bottom:1px solid var(--border-soft);flex-wrap:wrap;justify-content:space-between;align-items:flex-start;gap:30px;padding-bottom:34px;display:flex}.footer-copy{color:var(--text-dim);max-width:460px;font-size:13px;line-height:1.8}.footer-copy a{color:var(--text-muted);border-bottom:1px solid var(--border)}.footer-social{gap:12px;display:flex}.footer-social a{border:1px solid var(--border);width:38px;height:38px;color:var(--text);border-radius:9px;justify-content:center;align-items:center;transition:border-color .15s,color .15s;display:flex}.footer-social a:hover{border-color:var(--teal);color:var(--teal)}.footer-social svg{width:17px;height:17px}.footer-bottom{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:14px;padding-top:24px;display:flex}.footer-bottom .sponsor{font-family:var(--mono);color:var(--text-dim);font-size:12px}.footer-bottom .sponsor a{color:var(--text-muted);border-bottom:1px solid var(--border)}@media (width<=980px){.hero-grid{grid-template-columns:1fr;gap:44px}.hero{padding-top:44px}.split{grid-template-columns:1fr;gap:40px}.products-grid,.standards-grid{grid-template-columns:repeat(2,1fr)}.posts-grid{grid-template-columns:1fr}.about-inner{grid-template-columns:1fr;gap:26px}}@media (width<=700px){.wrap{padding:0 16px}.hero h1{font-size:32px}.products-grid,.standards-grid,.components-grid{grid-template-columns:1fr}section{padding:56px 0}.pkg-row{flex-direction:column;gap:2px}.pkg-row .pkg-desc{text-align:left}}:focus-visible{outline:2px solid var(--amber);outline-offset:2px}.icon{flex-shrink:0;width:18px;height:18px}.icon-lg{width:50px;height:50px}.icon-jetbrains{min-width:100px}[data-theme=light] .icon{filter:none}[data-theme=dark] .icon{filter:invert()}.contact-grid{grid-template-columns:1fr 1fr;gap:18px;display:grid}@media (width<=700px){.contact-grid{grid-template-columns:1fr}}.contact-card{text-align:center;align-items:center}.contact-card svg{width:40px;height:40px;color:var(--teal);margin-bottom:14px}.contact-card .card-btns{justify-content:center}.approach-card{margin-top:18px}.approach-card h3{margin-bottom:10px}.latest-posts{margin-top:48px}.latest-posts .section-head{margin-bottom:24px}.latest-posts .posts-grid{grid-template-columns:repeat(2,1fr)}@media (width<=700px){.latest-posts .posts-grid{grid-template-columns:1fr}}.sidebar{flex-direction:column;gap:34px;display:flex}@media (width<=900px){.sidebar{order:2}}.widget{border-bottom:1px solid var(--border-soft);padding-bottom:28px}.widget:last-child{border-bottom:none;padding-bottom:0}.widget ul{margin:0;padding:0;list-style:none}.widget-title{font-family:var(--mono);letter-spacing:.1em;text-transform:uppercase;color:var(--text-dim);margin-bottom:16px;font-size:11.5px}.search-form{border:1px solid var(--border);border-radius:8px;display:flex;overflow:hidden}.search-form input{min-width:0;color:var(--text);font-family:var(--sans);background:0 0;border:none;flex:1;padding:10px 12px;font-size:14px}.search-form input:focus{outline:none}.search-form button{background:var(--surface-2);color:var(--text-muted);cursor:pointer;font-family:var(--mono);border:none;padding:0 14px;font-size:12px}.search-form button:hover{color:var(--amber)}.widget-recent ul{flex-direction:column;gap:14px;display:flex}.widget-recent li a{color:var(--text);font-size:14px;line-height:1.4;display:block}.widget-recent li a:hover{color:var(--amber)}.widget-recent li .post-date{font-family:var(--mono);color:var(--text-dim);margin-top:4px;font-size:11px;display:block}.widget-categories li{border-bottom:1px solid var(--border-soft)}.widget-categories li:last-child{border-bottom:none}.widget-categories li.current a{color:var(--amber);font-weight:600}.widget-categories li a{color:var(--text-muted);justify-content:space-between;align-items:center;gap:10px;padding:9px 0;font-size:14px;display:flex}.widget-categories li a:hover{color:var(--text)}.widget-categories .cat-count{min-width:28px;font-family:var(--mono);text-align:center;color:var(--text-dim);background:var(--surface-2);border:1px solid var(--border-soft);border-radius:999px;flex-shrink:0;padding:1px 9px;font-size:11px;display:inline-block}.widget-categories li a:hover .cat-count{color:var(--text)}.widget-more{margin-top:14px}.widget-more a{font-family:var(--mono);color:var(--amber);font-size:12px}.widget-more a:hover{text-decoration:underline}.no-articles{color:var(--text-dim);font-size:13.5px}.widget-connect ul{flex-direction:column;gap:12px;display:flex}.widget-connect li a{color:var(--text-muted);align-items:center;gap:10px;font-size:14px;display:flex}.widget-connect li a:hover{color:var(--text)}.widget-connect li a svg{width:17px;height:17px;color:var(--teal);flex-shrink:0}.widget-approach p{color:var(--text-muted);margin-bottom:12px;font-size:13.5px;line-height:1.7}.widget-approach p:last-child{margin-bottom:0}.breadcrumb-back{font-family:var(--mono);color:var(--text-dim);align-items:center;gap:6px;font-size:13px;display:inline-flex}.breadcrumb-back:hover{color:var(--amber)}.breadcrumb-back svg{width:14px;height:14px}.post-hero{border-bottom:1px solid var(--border-soft);padding:40px 0}.post-hero h1{max-width:820px;margin-top:16px;font-size:38px;line-height:1.2}@media (width<=900px){.post-hero h1{font-size:28px}}.post-meta{font-family:var(--mono);color:var(--text-dim);flex-wrap:wrap;align-items:center;gap:6px 14px;margin-top:22px;font-size:13px;display:flex}.post-meta a{color:var(--text-muted)}.post-meta a:hover{color:var(--amber)}.post-meta .featured-tag{color:var(--brand-red);font-weight:600}.post-body-wrap{padding:52px 0 72px}.post-layout{grid-template-columns:270px 1fr;align-items:start;gap:64px;display:grid}@media (width<=900px){.post-layout{grid-template-columns:1fr;gap:44px}.article{order:1}}.entry{max-width:720px}@media (width<=900px){.entry{max-width:100%}}.entry h2{font-family:var(--mono);color:var(--text);margin-top:48px;margin-bottom:16px;font-size:26px}.entry h2:first-child{margin-top:0}.entry h3{font-family:var(--mono);color:var(--text);margin-top:36px;margin-bottom:14px;font-size:20px}.entry h4{font-family:var(--mono);color:var(--text);margin-top:28px;margin-bottom:12px;font-size:17px}.entry p{color:var(--text-muted);margin-bottom:20px;font-size:16.5px;line-height:1.8}.entry strong{color:var(--text);font-weight:600}.entry a{color:var(--teal);text-decoration:underline;-webkit-text-decoration-color:var(--border);text-decoration-color:var(--border);text-underline-offset:3px}.entry a:hover{color:var(--amber);-webkit-text-decoration-color:var(--amber);text-decoration-color:var(--amber)}.entry ul{margin:0 0 20px;padding-left:0;list-style:none}.entry ul li{color:var(--text-muted);margin-bottom:10px;padding-left:22px;font-size:16px;line-height:1.7;position:relative}.entry ul li:before{content:"";background:var(--amber);border-radius:50%;width:6px;height:6px;position:absolute;top:10px;left:0}.entry figure{margin:28px 0}.entry figure img{border:1px solid var(--border);border-radius:10px;width:100%;height:auto;display:block}.entry .img-row{gap:20px;margin:28px 0;display:flex}@media (width<=900px){.entry .img-row{flex-direction:column}}.entry .img-row figure{flex:1;margin:0}.entry .img-row img{width:100%}.entry code{font-family:var(--mono);background:var(--surface-2);border:1px solid var(--border);border-radius:4px;padding:2px 6px;font-size:.9em}.entry pre{font-family:var(--mono);background:var(--surface-2);border:1px solid var(--border);color:var(--text);border-radius:8px;margin:0 0 20px;padding:16px 18px;font-size:14px;line-height:1.6;overflow-x:auto}.entry pre code{font-size:inherit;background:0 0;border:none;padding:0}.obsolete-warning{border-radius:var(--radius);border:1px solid var(--brand-red);border-left:4px solid var(--brand-red);background:#c71f381f;margin:0 0 40px;padding:20px 24px}.obsolete-warning p{color:var(--text);margin:0;font-size:14.5px;line-height:1.7}.obsolete-warning strong{color:var(--brand-red)}.obsolete-warning a{color:var(--brand-red);text-decoration:underline;-webkit-text-decoration-color:var(--brand-red);text-decoration-color:var(--brand-red);text-underline-offset:3px;font-weight:600}.obsolete-warning a:hover{color:var(--teal);-webkit-text-decoration-color:var(--teal);text-decoration-color:var(--teal)}.callout{background:var(--surface);border:1px solid var(--border);border-left:3px solid var(--brand-red);border-radius:12px;margin:44px 0;padding:28px 30px}.callout h4{font-family:var(--mono);color:var(--text);margin-bottom:12px;font-size:18px}.callout p{color:var(--text-muted);margin-bottom:14px;font-size:14.5px;line-height:1.7}.callout ul{margin:0 0 20px;padding-left:0;list-style:none}.callout ul li{color:var(--text-muted);margin-bottom:8px;padding-left:20px;font-size:14.5px;position:relative}.callout ul li:before{content:"";background:var(--brand-red);border-radius:50%;width:5px;height:5px;position:absolute;top:9px;left:0}.post-nav{grid-template-columns:1fr 1fr;gap:16px;margin:48px 0;display:grid}@media (width<=900px){.post-nav{grid-template-columns:1fr}}.post-nav a{border:1px solid var(--border);background:var(--surface);border-radius:12px;padding:20px 22px;display:block}.post-nav a:hover{border-color:var(--text-dim)}.post-nav .direction{font-family:var(--mono);letter-spacing:.08em;text-transform:uppercase;color:var(--text-dim);margin-bottom:8px;font-size:11px;display:block}.post-nav .title{color:var(--text);font-size:14.5px;line-height:1.4}.post-nav .next-post{text-align:right}.comment-section{border-top:1px solid var(--border-soft);margin-top:56px;padding-top:40px}.comment-section h3{font-family:var(--mono);color:var(--text);margin-bottom:24px;font-size:20px}.comment-form-mock{flex-direction:column;gap:16px;max-width:560px;display:flex}.comment-form-mock label{font-family:var(--mono);text-transform:uppercase;letter-spacing:.06em;color:var(--text-dim);margin-bottom:6px;font-size:12px;display:block}.comment-form-mock input,.comment-form-mock textarea{background:var(--surface);border:1px solid var(--border);width:100%;color:var(--text);font-family:var(--sans);border-radius:8px;padding:11px 14px;font-size:14.5px}.comment-form-mock input:focus,.comment-form-mock textarea:focus{border-color:var(--amber);outline:none}.comment-form-mock textarea{resize:vertical;min-height:120px}.post-hero-v2{padding:20px 0 16px;position:relative}.post-hero-v2:after{content:"";background:linear-gradient(90deg, var(--amber), var(--teal), transparent 85%);border-radius:2px;height:2px;position:absolute;bottom:0;left:0;right:0}.post-hero-v2 h1{max-width:none;margin:12px 0 0;font-size:38px;line-height:1.2}@media (width<=900px){.post-hero-v2 h1{font-size:28px}}.hero-top-row{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:10px 18px;display:flex}.hero-badge{background:var(--teal-dim);border:1px solid var(--teal);color:var(--teal);font-family:var(--mono);letter-spacing:.08em;text-transform:uppercase;border-radius:999px;align-items:center;gap:8px;padding:6px 14px 6px 10px;font-size:12px;display:inline-flex}.hero-badge svg{flex-shrink:0;width:14px;height:14px}.post-meta-v2{font-family:var(--mono);color:var(--text-dim);flex-wrap:wrap;align-items:center;gap:8px 22px;font-size:13px;display:flex}.post-meta-v2 a{color:var(--text-muted)}.post-meta-v2 a:hover{color:var(--amber)}.meta-item{align-items:center;gap:6px;display:inline-flex}.meta-item svg{width:14px;height:14px;color:var(--text-dim);flex-shrink:0}.md-actions{display:inline-flex;position:relative}.md-actions-toggle{background:var(--surface);border:1px solid var(--border);color:var(--text-muted);font-family:var(--sans);cursor:pointer;border-radius:8px;align-items:stretch;padding:0;font-size:13px;transition:box-shadow .15s,border-color .15s,color .15s;display:inline-flex;overflow:hidden}.md-actions-toggle svg{flex-shrink:0;width:14px;height:14px}.md-actions-toggle .chev{width:12px;height:12px;transition:transform .15s}.md-actions-toggle:hover{border-color:var(--text-dim);color:var(--text);box-shadow:0 4px 12px #0000001f}.md-actions-toggle-label{align-items:center;gap:8px;padding:6px 12px;display:inline-flex}.md-actions-toggle-chev{border-left:1px solid var(--border);align-items:center;padding:6px 10px;display:inline-flex}.md-actions.open .md-actions-toggle{border-color:var(--text-dim);color:var(--text);box-shadow:0 4px 12px #0000001f}.md-actions.open .md-actions-toggle .chev{transform:rotate(180deg)}.md-actions-menu{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);z-index:20;flex-direction:column;gap:2px;min-width:240px;padding:6px;display:none;position:absolute;top:calc(100% + 8px);right:0;box-shadow:0 12px 32px #0000002e}.md-actions.open .md-actions-menu{display:flex}.md-actions-item{color:var(--text);font-family:var(--sans);text-align:left;cursor:pointer;background:0 0;border:none;border-radius:8px;align-items:center;gap:10px;padding:8px 10px;font-size:13px;text-decoration:none;display:flex}.md-actions-item svg{width:16px;height:16px;color:var(--text-dim);flex-shrink:0}.md-actions-item:hover{background:var(--surface-2);color:var(--amber)}.md-actions-item:hover svg{color:var(--amber)}.md-actions-bar{justify-content:flex-end;margin-bottom:12px;display:flex}.md-actions-bar+h2{margin-top:0} +:root,[data-bs-theme=light]{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-black:#000;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-gray-100:#f8f9fa;--bs-gray-200:#e9ecef;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#adb5bd;--bs-gray-600:#6c757d;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-primary:#0d6efd;--bs-secondary:#6c757d;--bs-success:#198754;--bs-info:#0dcaf0;--bs-warning:#ffc107;--bs-danger:#dc3545;--bs-light:#f8f9fa;--bs-dark:#212529;--bs-primary-rgb:13, 110, 253;--bs-secondary-rgb:108, 117, 125;--bs-success-rgb:25, 135, 84;--bs-info-rgb:13, 202, 240;--bs-warning-rgb:255, 193, 7;--bs-danger-rgb:220, 53, 69;--bs-light-rgb:248, 249, 250;--bs-dark-rgb:33, 37, 41;--bs-primary-text-emphasis:#052c65;--bs-secondary-text-emphasis:#2b2f32;--bs-success-text-emphasis:#0a3622;--bs-info-text-emphasis:#055160;--bs-warning-text-emphasis:#664d03;--bs-danger-text-emphasis:#58151c;--bs-light-text-emphasis:#495057;--bs-dark-text-emphasis:#495057;--bs-primary-bg-subtle:#cfe2ff;--bs-secondary-bg-subtle:#e2e3e5;--bs-success-bg-subtle:#d1e7dd;--bs-info-bg-subtle:#cff4fc;--bs-warning-bg-subtle:#fff3cd;--bs-danger-bg-subtle:#f8d7da;--bs-light-bg-subtle:#fcfcfd;--bs-dark-bg-subtle:#ced4da;--bs-primary-border-subtle:#9ec5fe;--bs-secondary-border-subtle:#c4c8cb;--bs-success-border-subtle:#a3cfbb;--bs-info-border-subtle:#9eeaf9;--bs-warning-border-subtle:#ffe69c;--bs-danger-border-subtle:#f1aeb5;--bs-light-border-subtle:#e9ecef;--bs-dark-border-subtle:#adb5bd;--bs-white-rgb:255, 255, 255;--bs-black-rgb:0, 0, 0;--bs-font-sans-serif:system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--bs-font-monospace:SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--bs-gradient:linear-gradient(180deg, #ffffff26, #fff0);--bs-body-font-family:var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight:400;--bs-body-line-height:1.5;--bs-body-color:#212529;--bs-body-color-rgb:33, 37, 41;--bs-body-bg:#fff;--bs-body-bg-rgb:255, 255, 255;--bs-emphasis-color:#000;--bs-emphasis-color-rgb:0, 0, 0;--bs-secondary-color:#212529bf;--bs-secondary-color-rgb:33, 37, 41;--bs-secondary-bg:#e9ecef;--bs-secondary-bg-rgb:233, 236, 239;--bs-tertiary-color:#21252980;--bs-tertiary-color-rgb:33, 37, 41;--bs-tertiary-bg:#f8f9fa;--bs-tertiary-bg-rgb:248, 249, 250;--bs-heading-color:inherit;--bs-link-color:#0d6efd;--bs-link-color-rgb:13, 110, 253;--bs-link-decoration:underline;--bs-link-hover-color:#0a58ca;--bs-link-hover-color-rgb:10, 88, 202;--bs-code-color:#d63384;--bs-highlight-color:#212529;--bs-highlight-bg:#fff3cd;--bs-border-width:1px;--bs-border-style:solid;--bs-border-color:#dee2e6;--bs-border-color-translucent:#0000002d;--bs-border-radius:.375rem;--bs-border-radius-sm:.25rem;--bs-border-radius-lg:.5rem;--bs-border-radius-xl:1rem;--bs-border-radius-xxl:2rem;--bs-border-radius-2xl:var(--bs-border-radius-xxl);--bs-border-radius-pill:50rem;--bs-box-shadow:0 .5rem 1rem #00000026;--bs-box-shadow-sm:0 .125rem .25rem #00000013;--bs-box-shadow-lg:0 1rem 3rem #0000002d;--bs-box-shadow-inset:inset 0 1px 2px #00000013;--bs-focus-ring-width:.25rem;--bs-focus-ring-opacity:.25;--bs-focus-ring-color:#0d6efd40;--bs-form-valid-color:#198754;--bs-form-valid-border-color:#198754;--bs-form-invalid-color:#dc3545;--bs-form-invalid-border-color:#dc3545}[data-bs-theme=dark]{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;--bs-body-color:#dee2e6;--bs-body-color-rgb:222, 226, 230;--bs-body-bg:#212529;--bs-body-bg-rgb:33, 37, 41;--bs-emphasis-color:#fff;--bs-emphasis-color-rgb:255, 255, 255;--bs-secondary-color:#dee2e6bf;--bs-secondary-color-rgb:222, 226, 230;--bs-secondary-bg:#343a40;--bs-secondary-bg-rgb:52, 58, 64;--bs-tertiary-color:#dee2e680;--bs-tertiary-color-rgb:222, 226, 230;--bs-tertiary-bg:#2b3035;--bs-tertiary-bg-rgb:43, 48, 53;--bs-primary-text-emphasis:#6ea8fe;--bs-secondary-text-emphasis:#a7acb1;--bs-success-text-emphasis:#75b798;--bs-info-text-emphasis:#6edff6;--bs-warning-text-emphasis:#ffda6a;--bs-danger-text-emphasis:#ea868f;--bs-light-text-emphasis:#f8f9fa;--bs-dark-text-emphasis:#dee2e6;--bs-primary-bg-subtle:#031633;--bs-secondary-bg-subtle:#161719;--bs-success-bg-subtle:#051b11;--bs-info-bg-subtle:#032830;--bs-warning-bg-subtle:#332701;--bs-danger-bg-subtle:#2c0b0e;--bs-light-bg-subtle:#343a40;--bs-dark-bg-subtle:#1a1d20;--bs-primary-border-subtle:#084298;--bs-secondary-border-subtle:#41464b;--bs-success-border-subtle:#0f5132;--bs-info-border-subtle:#087990;--bs-warning-border-subtle:#997404;--bs-danger-border-subtle:#842029;--bs-light-border-subtle:#495057;--bs-dark-border-subtle:#343a40;--bs-heading-color:inherit;--bs-link-color:#6ea8fe;--bs-link-hover-color:#8bb9fe;--bs-link-color-rgb:110, 168, 254;--bs-link-hover-color-rgb:139, 185, 254;--bs-code-color:#e685b5;--bs-highlight-color:#dee2e6;--bs-highlight-bg:#664d03;--bs-border-color:#495057;--bs-border-color-translucent:#ffffff26;--bs-form-valid-color:#75b798;--bs-form-valid-border-color:#75b798;--bs-form-invalid-color:#ea868f;--bs-form-invalid-border-color:#ea868f}*,:before,:after{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:#0000;margin:0}hr{color:inherit;border:0;border-top:var(--bs-border-width) solid;opacity:.25;margin:1rem 0}h6,.h6,h5,.h5,h4,.h4,h3,.h3,h2,.h2,h1,.h1{color:var(--bs-heading-color);margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}h1,.h1{font-size:calc(1.375rem + 1.5vw)}@media (width>=1200px){h1,.h1{font-size:2.5rem}}h2,.h2{font-size:calc(1.325rem + .9vw)}@media (width>=1200px){h2,.h2{font-size:2rem}}h3,.h3{font-size:calc(1.3rem + .6vw)}@media (width>=1200px){h3,.h3{font-size:1.75rem}}h4,.h4{font-size:calc(1.275rem + .3vw)}@media (width>=1200px){h4,.h4{font-size:1.5rem}}h5,.h5{font-size:1.25rem}h6,.h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[title]{cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}address{font-style:normal;line-height:inherit;margin-bottom:1rem}ol,ul{padding-left:2rem}ol,ul,dl{margin-top:0;margin-bottom:1rem}ol ol,ul ul,ol ul,ul ol{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small,.small{font-size:.875em}mark,.mark{color:var(--bs-highlight-color);background-color:var(--bs-highlight-bg);padding:.1875em}sub,sup{vertical-align:baseline;font-size:.75em;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}a:hover{--bs-link-color-rgb:var(--bs-link-hover-color-rgb)}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}pre,code,kbd,samp{font-family:var(--bs-font-monospace);font-size:1em}pre{margin-top:0;margin-bottom:1rem;font-size:.875em;display:block;overflow:auto}pre code{font-size:inherit;color:inherit;word-break:normal}code{color:var(--bs-code-color);word-wrap:break-word;font-size:.875em}a>code{color:inherit}kbd{color:var(--bs-body-bg);background-color:var(--bs-body-color);border-radius:.25rem;padding:.1875rem .375rem;font-size:.875em}kbd kbd{padding:0;font-size:1em}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{color:var(--bs-secondary-color);text-align:left;padding-top:.5rem;padding-bottom:.5rem}th{text-align:inherit;text-align:-webkit-match-parent}thead,tbody,tfoot,tr,td,th{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}input,button,select,optgroup,textarea{font-family:inherit;font-size:inherit;line-height:inherit;margin:0}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator{display:none!important}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}button:not(:disabled),[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled){cursor:pointer}::-moz-focus-inner{border-style:none;padding:0}textarea{resize:vertical}fieldset{border:0;min-width:0;margin:0;padding:0}legend{float:left;width:100%;line-height:inherit;margin-bottom:.5rem;padding:0;font-size:calc(1.275rem + .3vw)}@media (width>=1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit-text{padding:0}::-webkit-datetime-edit-minute{padding:0}::-webkit-datetime-edit-hour-field{padding:0}::-webkit-datetime-edit-day-field{padding:0}::-webkit-datetime-edit-month-field{padding:0}::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button{cursor:pointer;filter:grayscale()}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{cursor:pointer;display:list-item}progress{vertical-align:baseline}[hidden]{display:none!important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media (width>=1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media (width>=1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media (width>=1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media (width>=1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media (width>=1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media (width>=1200px){.display-6{font-size:2.5rem}}.list-unstyled,.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{text-transform:uppercase;font-size:.875em}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{color:#6c757d;margin-top:-1rem;margin-bottom:1rem;font-size:.875em}.blockquote-footer:before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{background-color:var(--bs-body-bg);border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius);max-width:100%;height:auto;padding:.25rem}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{color:var(--bs-secondary-color);font-size:.875em}.container,.container-fluid,.container-xxl,.container-xl,.container-lg,.container-md,.container-sm{--bs-gutter-x:1.5rem;--bs-gutter-y:0;width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-left:auto;margin-right:auto}@media (width>=576px){.container-sm,.container{max-width:540px}}@media (width>=768px){.container-md,.container-sm,.container{max-width:720px}}@media (width>=992px){.container-lg,.container-md,.container-sm,.container{max-width:960px}}@media (width>=1200px){.container-xl,.container-lg,.container-md,.container-sm,.container{max-width:1140px}}@media (width>=1400px){.container-xxl,.container-xl,.container-lg,.container-md,.container-sm,.container{max-width:1320px}}:root{--bs-breakpoint-xs:0;--bs-breakpoint-sm:576px;--bs-breakpoint-md:768px;--bs-breakpoint-lg:992px;--bs-breakpoint-xl:1200px;--bs-breakpoint-xxl:1400px}.row{--bs-gutter-x:1.5rem;--bs-gutter-y:0;margin-top:calc(-1 * var(--bs-gutter-y));margin-right:calc(-.5 * var(--bs-gutter-x));margin-left:calc(-.5 * var(--bs-gutter-x));flex-wrap:wrap;display:flex}.row>*{width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-top:var(--bs-gutter-y);flex-shrink:0}.col{flex:1 0 0}.row-cols-auto>*{flex:none;width:auto}.row-cols-1>*{flex:none;width:100%}.row-cols-2>*{flex:none;width:50%}.row-cols-3>*{flex:none;width:33.3333%}.row-cols-4>*{flex:none;width:25%}.row-cols-5>*{flex:none;width:20%}.row-cols-6>*{flex:none;width:16.6667%}.col-auto{flex:none;width:auto}.col-1{flex:none;width:8.33333%}.col-2{flex:none;width:16.6667%}.col-3{flex:none;width:25%}.col-4{flex:none;width:33.3333%}.col-5{flex:none;width:41.6667%}.col-6{flex:none;width:50%}.col-7{flex:none;width:58.3333%}.col-8{flex:none;width:66.6667%}.col-9{flex:none;width:75%}.col-10{flex:none;width:83.3333%}.col-11{flex:none;width:91.6667%}.col-12{flex:none;width:100%}.offset-1{margin-left:8.33333%}.offset-2{margin-left:16.6667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.3333%}.offset-5{margin-left:41.6667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.3333%}.offset-8{margin-left:66.6667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.3333%}.offset-11{margin-left:91.6667%}.g-0,.gx-0{--bs-gutter-x:0}.g-0,.gy-0{--bs-gutter-y:0}.g-1,.gx-1{--bs-gutter-x:.25rem}.g-1,.gy-1{--bs-gutter-y:.25rem}.g-2,.gx-2{--bs-gutter-x:.5rem}.g-2,.gy-2{--bs-gutter-y:.5rem}.g-3,.gx-3{--bs-gutter-x:1rem}.g-3,.gy-3{--bs-gutter-y:1rem}.g-4,.gx-4{--bs-gutter-x:1.5rem}.g-4,.gy-4{--bs-gutter-y:1.5rem}.g-5,.gx-5{--bs-gutter-x:3rem}.g-5,.gy-5{--bs-gutter-y:3rem}@media (width>=576px){.col-sm{flex:1 0 0}.row-cols-sm-auto>*{flex:none;width:auto}.row-cols-sm-1>*{flex:none;width:100%}.row-cols-sm-2>*{flex:none;width:50%}.row-cols-sm-3>*{flex:none;width:33.3333%}.row-cols-sm-4>*{flex:none;width:25%}.row-cols-sm-5>*{flex:none;width:20%}.row-cols-sm-6>*{flex:none;width:16.6667%}.col-sm-auto{flex:none;width:auto}.col-sm-1{flex:none;width:8.33333%}.col-sm-2{flex:none;width:16.6667%}.col-sm-3{flex:none;width:25%}.col-sm-4{flex:none;width:33.3333%}.col-sm-5{flex:none;width:41.6667%}.col-sm-6{flex:none;width:50%}.col-sm-7{flex:none;width:58.3333%}.col-sm-8{flex:none;width:66.6667%}.col-sm-9{flex:none;width:75%}.col-sm-10{flex:none;width:83.3333%}.col-sm-11{flex:none;width:91.6667%}.col-sm-12{flex:none;width:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333%}.offset-sm-2{margin-left:16.6667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.3333%}.offset-sm-5{margin-left:41.6667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.3333%}.offset-sm-8{margin-left:66.6667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.3333%}.offset-sm-11{margin-left:91.6667%}.g-sm-0,.gx-sm-0{--bs-gutter-x:0}.g-sm-0,.gy-sm-0{--bs-gutter-y:0}.g-sm-1,.gx-sm-1{--bs-gutter-x:.25rem}.g-sm-1,.gy-sm-1{--bs-gutter-y:.25rem}.g-sm-2,.gx-sm-2{--bs-gutter-x:.5rem}.g-sm-2,.gy-sm-2{--bs-gutter-y:.5rem}.g-sm-3,.gx-sm-3{--bs-gutter-x:1rem}.g-sm-3,.gy-sm-3{--bs-gutter-y:1rem}.g-sm-4,.gx-sm-4{--bs-gutter-x:1.5rem}.g-sm-4,.gy-sm-4{--bs-gutter-y:1.5rem}.g-sm-5,.gx-sm-5{--bs-gutter-x:3rem}.g-sm-5,.gy-sm-5{--bs-gutter-y:3rem}}@media (width>=768px){.col-md{flex:1 0 0}.row-cols-md-auto>*{flex:none;width:auto}.row-cols-md-1>*{flex:none;width:100%}.row-cols-md-2>*{flex:none;width:50%}.row-cols-md-3>*{flex:none;width:33.3333%}.row-cols-md-4>*{flex:none;width:25%}.row-cols-md-5>*{flex:none;width:20%}.row-cols-md-6>*{flex:none;width:16.6667%}.col-md-auto{flex:none;width:auto}.col-md-1{flex:none;width:8.33333%}.col-md-2{flex:none;width:16.6667%}.col-md-3{flex:none;width:25%}.col-md-4{flex:none;width:33.3333%}.col-md-5{flex:none;width:41.6667%}.col-md-6{flex:none;width:50%}.col-md-7{flex:none;width:58.3333%}.col-md-8{flex:none;width:66.6667%}.col-md-9{flex:none;width:75%}.col-md-10{flex:none;width:83.3333%}.col-md-11{flex:none;width:91.6667%}.col-md-12{flex:none;width:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333%}.offset-md-2{margin-left:16.6667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.3333%}.offset-md-5{margin-left:41.6667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.3333%}.offset-md-8{margin-left:66.6667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.3333%}.offset-md-11{margin-left:91.6667%}.g-md-0,.gx-md-0{--bs-gutter-x:0}.g-md-0,.gy-md-0{--bs-gutter-y:0}.g-md-1,.gx-md-1{--bs-gutter-x:.25rem}.g-md-1,.gy-md-1{--bs-gutter-y:.25rem}.g-md-2,.gx-md-2{--bs-gutter-x:.5rem}.g-md-2,.gy-md-2{--bs-gutter-y:.5rem}.g-md-3,.gx-md-3{--bs-gutter-x:1rem}.g-md-3,.gy-md-3{--bs-gutter-y:1rem}.g-md-4,.gx-md-4{--bs-gutter-x:1.5rem}.g-md-4,.gy-md-4{--bs-gutter-y:1.5rem}.g-md-5,.gx-md-5{--bs-gutter-x:3rem}.g-md-5,.gy-md-5{--bs-gutter-y:3rem}}@media (width>=992px){.col-lg{flex:1 0 0}.row-cols-lg-auto>*{flex:none;width:auto}.row-cols-lg-1>*{flex:none;width:100%}.row-cols-lg-2>*{flex:none;width:50%}.row-cols-lg-3>*{flex:none;width:33.3333%}.row-cols-lg-4>*{flex:none;width:25%}.row-cols-lg-5>*{flex:none;width:20%}.row-cols-lg-6>*{flex:none;width:16.6667%}.col-lg-auto{flex:none;width:auto}.col-lg-1{flex:none;width:8.33333%}.col-lg-2{flex:none;width:16.6667%}.col-lg-3{flex:none;width:25%}.col-lg-4{flex:none;width:33.3333%}.col-lg-5{flex:none;width:41.6667%}.col-lg-6{flex:none;width:50%}.col-lg-7{flex:none;width:58.3333%}.col-lg-8{flex:none;width:66.6667%}.col-lg-9{flex:none;width:75%}.col-lg-10{flex:none;width:83.3333%}.col-lg-11{flex:none;width:91.6667%}.col-lg-12{flex:none;width:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333%}.offset-lg-2{margin-left:16.6667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.3333%}.offset-lg-5{margin-left:41.6667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.3333%}.offset-lg-8{margin-left:66.6667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.3333%}.offset-lg-11{margin-left:91.6667%}.g-lg-0,.gx-lg-0{--bs-gutter-x:0}.g-lg-0,.gy-lg-0{--bs-gutter-y:0}.g-lg-1,.gx-lg-1{--bs-gutter-x:.25rem}.g-lg-1,.gy-lg-1{--bs-gutter-y:.25rem}.g-lg-2,.gx-lg-2{--bs-gutter-x:.5rem}.g-lg-2,.gy-lg-2{--bs-gutter-y:.5rem}.g-lg-3,.gx-lg-3{--bs-gutter-x:1rem}.g-lg-3,.gy-lg-3{--bs-gutter-y:1rem}.g-lg-4,.gx-lg-4{--bs-gutter-x:1.5rem}.g-lg-4,.gy-lg-4{--bs-gutter-y:1.5rem}.g-lg-5,.gx-lg-5{--bs-gutter-x:3rem}.g-lg-5,.gy-lg-5{--bs-gutter-y:3rem}}@media (width>=1200px){.col-xl{flex:1 0 0}.row-cols-xl-auto>*{flex:none;width:auto}.row-cols-xl-1>*{flex:none;width:100%}.row-cols-xl-2>*{flex:none;width:50%}.row-cols-xl-3>*{flex:none;width:33.3333%}.row-cols-xl-4>*{flex:none;width:25%}.row-cols-xl-5>*{flex:none;width:20%}.row-cols-xl-6>*{flex:none;width:16.6667%}.col-xl-auto{flex:none;width:auto}.col-xl-1{flex:none;width:8.33333%}.col-xl-2{flex:none;width:16.6667%}.col-xl-3{flex:none;width:25%}.col-xl-4{flex:none;width:33.3333%}.col-xl-5{flex:none;width:41.6667%}.col-xl-6{flex:none;width:50%}.col-xl-7{flex:none;width:58.3333%}.col-xl-8{flex:none;width:66.6667%}.col-xl-9{flex:none;width:75%}.col-xl-10{flex:none;width:83.3333%}.col-xl-11{flex:none;width:91.6667%}.col-xl-12{flex:none;width:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333%}.offset-xl-2{margin-left:16.6667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.3333%}.offset-xl-5{margin-left:41.6667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.3333%}.offset-xl-8{margin-left:66.6667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.3333%}.offset-xl-11{margin-left:91.6667%}.g-xl-0,.gx-xl-0{--bs-gutter-x:0}.g-xl-0,.gy-xl-0{--bs-gutter-y:0}.g-xl-1,.gx-xl-1{--bs-gutter-x:.25rem}.g-xl-1,.gy-xl-1{--bs-gutter-y:.25rem}.g-xl-2,.gx-xl-2{--bs-gutter-x:.5rem}.g-xl-2,.gy-xl-2{--bs-gutter-y:.5rem}.g-xl-3,.gx-xl-3{--bs-gutter-x:1rem}.g-xl-3,.gy-xl-3{--bs-gutter-y:1rem}.g-xl-4,.gx-xl-4{--bs-gutter-x:1.5rem}.g-xl-4,.gy-xl-4{--bs-gutter-y:1.5rem}.g-xl-5,.gx-xl-5{--bs-gutter-x:3rem}.g-xl-5,.gy-xl-5{--bs-gutter-y:3rem}}@media (width>=1400px){.col-xxl{flex:1 0 0}.row-cols-xxl-auto>*{flex:none;width:auto}.row-cols-xxl-1>*{flex:none;width:100%}.row-cols-xxl-2>*{flex:none;width:50%}.row-cols-xxl-3>*{flex:none;width:33.3333%}.row-cols-xxl-4>*{flex:none;width:25%}.row-cols-xxl-5>*{flex:none;width:20%}.row-cols-xxl-6>*{flex:none;width:16.6667%}.col-xxl-auto{flex:none;width:auto}.col-xxl-1{flex:none;width:8.33333%}.col-xxl-2{flex:none;width:16.6667%}.col-xxl-3{flex:none;width:25%}.col-xxl-4{flex:none;width:33.3333%}.col-xxl-5{flex:none;width:41.6667%}.col-xxl-6{flex:none;width:50%}.col-xxl-7{flex:none;width:58.3333%}.col-xxl-8{flex:none;width:66.6667%}.col-xxl-9{flex:none;width:75%}.col-xxl-10{flex:none;width:83.3333%}.col-xxl-11{flex:none;width:91.6667%}.col-xxl-12{flex:none;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:8.33333%}.offset-xxl-2{margin-left:16.6667%}.offset-xxl-3{margin-left:25%}.offset-xxl-4{margin-left:33.3333%}.offset-xxl-5{margin-left:41.6667%}.offset-xxl-6{margin-left:50%}.offset-xxl-7{margin-left:58.3333%}.offset-xxl-8{margin-left:66.6667%}.offset-xxl-9{margin-left:75%}.offset-xxl-10{margin-left:83.3333%}.offset-xxl-11{margin-left:91.6667%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x:0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y:0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x:.25rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y:.25rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x:.5rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y:.5rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x:1rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y:1rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x:1.5rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y:1.5rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x:3rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y:3rem}}.table{--bs-table-color-type:initial;--bs-table-bg-type:initial;--bs-table-color-state:initial;--bs-table-bg-state:initial;--bs-table-color:var(--bs-emphasis-color);--bs-table-bg:var(--bs-body-bg);--bs-table-border-color:var(--bs-border-color);--bs-table-accent-bg:transparent;--bs-table-striped-color:var(--bs-emphasis-color);--bs-table-striped-bg:rgba(var(--bs-emphasis-color-rgb), .05);--bs-table-active-color:var(--bs-emphasis-color);--bs-table-active-bg:rgba(var(--bs-emphasis-color-rgb), .1);--bs-table-hover-color:var(--bs-emphasis-color);--bs-table-hover-bg:rgba(var(--bs-emphasis-color-rgb), .075);vertical-align:top;border-color:var(--bs-table-border-color);width:100%;margin-bottom:1rem}.table>:not(caption)>*>*{color:var(--bs-table-color-state,var(--bs-table-color-type,var(--bs-table-color)));background-color:var(--bs-table-bg);border-bottom-width:var(--bs-border-width);box-shadow:inset 0 0 0 9999px var(--bs-table-bg-state,var(--bs-table-bg-type,var(--bs-table-accent-bg)));padding:.5rem}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table-group-divider{border-top:calc(var(--bs-border-width) * 2) solid currentcolor}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem}.table-bordered>:not(caption)>*{border-width:var(--bs-border-width) 0}.table-bordered>:not(caption)>*>*{border-width:0 var(--bs-border-width)}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-borderless>:not(:first-child){border-top-width:0}.table-striped>tbody>tr:nth-of-type(odd)>*,.table-striped-columns>:not(caption)>tr>:nth-child(2n){--bs-table-color-type:var(--bs-table-striped-color);--bs-table-bg-type:var(--bs-table-striped-bg)}.table-active{--bs-table-color-state:var(--bs-table-active-color);--bs-table-bg-state:var(--bs-table-active-bg)}.table-hover>tbody>tr:hover>*{--bs-table-color-state:var(--bs-table-hover-color);--bs-table-bg-state:var(--bs-table-hover-bg)}.table-primary{--bs-table-color:#000;--bs-table-bg:#cfe2ff;--bs-table-border-color:#a5b5cc;--bs-table-striped-bg:#c4d7f2;--bs-table-striped-color:#000;--bs-table-active-bg:#bacbe5;--bs-table-active-color:#000;--bs-table-hover-bg:#bfd1ec;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-secondary{--bs-table-color:#000;--bs-table-bg:#e2e3e5;--bs-table-border-color:#b4b6b7;--bs-table-striped-bg:#d6d8da;--bs-table-striped-color:#000;--bs-table-active-bg:#cbcdce;--bs-table-active-color:#000;--bs-table-hover-bg:#d1d2d4;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-success{--bs-table-color:#000;--bs-table-bg:#d1e7dd;--bs-table-border-color:#a7b9b1;--bs-table-striped-bg:#c7dbd2;--bs-table-striped-color:#000;--bs-table-active-bg:#bcd0c7;--bs-table-active-color:#000;--bs-table-hover-bg:#c1d6cc;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-info{--bs-table-color:#000;--bs-table-bg:#cff4fc;--bs-table-border-color:#a5c4ca;--bs-table-striped-bg:#c4e8ef;--bs-table-striped-color:#000;--bs-table-active-bg:#badce3;--bs-table-active-color:#000;--bs-table-hover-bg:#bfe2e9;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-warning{--bs-table-color:#000;--bs-table-bg:#fff3cd;--bs-table-border-color:#ccc2a4;--bs-table-striped-bg:#f2e6c3;--bs-table-striped-color:#000;--bs-table-active-bg:#e6dab9;--bs-table-active-color:#000;--bs-table-hover-bg:#ece0be;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-danger{--bs-table-color:#000;--bs-table-bg:#f8d7da;--bs-table-border-color:#c6acae;--bs-table-striped-bg:#eccccf;--bs-table-striped-color:#000;--bs-table-active-bg:#dfc1c4;--bs-table-active-color:#000;--bs-table-hover-bg:#e5c7c9;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-light{--bs-table-color:#000;--bs-table-bg:#f8f9fa;--bs-table-border-color:#c6c7c8;--bs-table-striped-bg:#ecedee;--bs-table-striped-color:#000;--bs-table-active-bg:#dfe0e1;--bs-table-active-color:#000;--bs-table-hover-bg:#e5e6e7;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-dark{--bs-table-color:#fff;--bs-table-bg:#212529;--bs-table-border-color:#4d5154;--bs-table-striped-bg:#2c3034;--bs-table-striped-color:#fff;--bs-table-active-bg:#373b3e;--bs-table-active-color:#fff;--bs-table-hover-bg:#323539;--bs-table-hover-color:#fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-responsive{-webkit-overflow-scrolling:touch;overflow-x:auto}@media (width<=575.98px){.table-responsive-sm{-webkit-overflow-scrolling:touch;overflow-x:auto}}@media (width<=767.98px){.table-responsive-md{-webkit-overflow-scrolling:touch;overflow-x:auto}}@media (width<=991.98px){.table-responsive-lg{-webkit-overflow-scrolling:touch;overflow-x:auto}}@media (width<=1199.98px){.table-responsive-xl{-webkit-overflow-scrolling:touch;overflow-x:auto}}@media (width<=1399.98px){.table-responsive-xxl{-webkit-overflow-scrolling:touch;overflow-x:auto}}.form-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(.375rem + var(--bs-border-width));padding-bottom:calc(.375rem + var(--bs-border-width));font-size:inherit;margin-bottom:0;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + var(--bs-border-width));padding-bottom:calc(.5rem + var(--bs-border-width));font-size:1.25rem}.col-form-label-sm{padding-top:calc(.25rem + var(--bs-border-width));padding-bottom:calc(.25rem + var(--bs-border-width));font-size:.875rem}.form-text{color:var(--bs-secondary-color);margin-top:.25rem;font-size:.875em}.form-control{width:100%;color:var(--bs-body-color);appearance:none;background-color:var(--bs-body-bg);border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius);background-clip:padding-box;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;display:block}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:var(--bs-body-color);background-color:var(--bs-body-bg);border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem #0d6efd40}.form-control::-webkit-date-and-time-value{min-width:85px;height:1.5em;margin:0}.form-control::-webkit-datetime-edit{padding:0;display:block}.form-control::placeholder{color:var(--bs-secondary-color);opacity:1}.form-control:disabled{background-color:var(--bs-secondary-bg);opacity:1}.form-control::file-selector-button{margin:-.375rem -.75rem;color:var(--bs-body-color);background-color:var(--bs-tertiary-bg);pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:var(--bs-border-width);border-radius:0;margin-inline-end:.75rem;padding:.375rem .75rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:var(--bs-secondary-bg)}.form-control-plaintext{width:100%;color:var(--bs-body-color);border:solid #0000;border-width:var(--bs-border-width) 0;background-color:#0000;margin-bottom:0;padding:.375rem 0;line-height:1.5;display:block}.form-control-plaintext:focus{outline:0}.form-control-plaintext.form-control-sm,.form-control-plaintext.form-control-lg{padding-left:0;padding-right:0}.form-control-sm{min-height:calc(1.5em + .5rem + calc(var(--bs-border-width) * 2));border-radius:var(--bs-border-radius-sm);padding:.25rem .5rem;font-size:.875rem}.form-control-sm::file-selector-button{margin:-.25rem -.5rem;margin-inline-end:.5rem;padding:.25rem .5rem}.form-control-lg{min-height:calc(1.5em + 1rem + calc(var(--bs-border-width) * 2));border-radius:var(--bs-border-radius-lg);padding:.5rem 1rem;font-size:1.25rem}.form-control-lg::file-selector-button{margin:-.5rem -1rem;margin-inline-end:1rem;padding:.5rem 1rem}textarea.form-control{min-height:calc(1.5em + .75rem + calc(var(--bs-border-width) * 2))}textarea.form-control-sm{min-height:calc(1.5em + .5rem + calc(var(--bs-border-width) * 2))}textarea.form-control-lg{min-height:calc(1.5em + 1rem + calc(var(--bs-border-width) * 2))}.form-control-color{width:3rem;height:calc(1.5em + .75rem + calc(var(--bs-border-width) * 2));padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{border-radius:var(--bs-border-radius);border:0!important}.form-control-color::-webkit-color-swatch{border-radius:var(--bs-border-radius);border:0!important}.form-control-color.form-control-sm{height:calc(1.5em + .5rem + calc(var(--bs-border-width) * 2))}.form-control-color.form-control-lg{height:calc(1.5em + 1rem + calc(var(--bs-border-width) * 2))}.form-select{--bs-form-select-bg-img:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");width:100%;color:var(--bs-body-color);appearance:none;background-color:var(--bs-body-bg);background-image:var(--bs-form-select-bg-img), var(--bs-form-select-bg-icon,none);border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius);background-position:right .75rem center;background-repeat:no-repeat;background-size:16px 12px;padding:.375rem 2.25rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;display:block}@media (prefers-reduced-motion:reduce){.form-select{transition:none}}.form-select:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem #0d6efd40}.form-select[multiple],.form-select[size]:not([size="1"]){background-image:none;padding-right:.75rem}.form-select:disabled{background-color:var(--bs-secondary-bg)}.form-select:-moz-focusring{color:#0000;text-shadow:0 0 0 var(--bs-body-color)}.form-select-sm{border-radius:var(--bs-border-radius-sm);padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.form-select-lg{border-radius:var(--bs-border-radius-lg);padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}[data-bs-theme=dark] .form-select{--bs-form-select-bg-img:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23dee2e6' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e")}.form-check{min-height:1.5rem;margin-bottom:.125rem;padding-left:1.5em;display:block}.form-check .form-check-input{float:left;margin-left:-1.5em}.form-check-reverse{text-align:right;padding-left:0;padding-right:1.5em}.form-check-reverse .form-check-input{float:right;margin-left:0;margin-right:-1.5em}.form-check-input{--bs-form-check-bg:var(--bs-body-bg);vertical-align:top;appearance:none;background-color:var(--bs-form-check-bg);background-image:var(--bs-form-check-bg-image);border:var(--bs-border-width) solid var(--bs-border-color);-webkit-print-color-adjust:exact;print-color-adjust:exact;background-position:50%;background-repeat:no-repeat;background-size:contain;flex-shrink:0;width:1em;height:1em;margin-top:.25em}.form-check-input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio]{border-radius:50%}.form-check-input:active{filter:brightness(90%)}.form-check-input:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem #0d6efd40}.form-check-input:checked{background-color:#0d6efd;border-color:#0d6efd}.form-check-input:checked[type=checkbox]{--bs-form-check-bg-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio]{--bs-form-check-bg-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate{--bs-form-check-bg-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e");background-color:#0d6efd;border-color:#0d6efd}.form-check-input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input[disabled]~.form-check-label,.form-check-input:disabled~.form-check-label{cursor:default;opacity:.5}.form-switch{padding-left:2.5em}.form-switch .form-check-input{--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");background-image:var(--bs-form-switch-bg);background-position:0;border-radius:2em;width:2em;margin-left:-2.5em;transition:background-position .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgb%2852.5490196078%, 71.568627451%, 99.6078431373%%29'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e");background-position:100%}.form-switch.form-check-reverse{padding-left:0;padding-right:2.5em}.form-switch.form-check-reverse .form-check-input{margin-left:0;margin-right:-2.5em}.form-check-inline{margin-right:1rem;display:inline-block}.btn-check{clip:rect(0, 0, 0, 0);pointer-events:none;position:absolute}.btn-check[disabled]+.btn,.btn-check:disabled+.btn{pointer-events:none;filter:none;opacity:.65}[data-bs-theme=dark] .form-switch .form-check-input:not(:checked):not(:focus){--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%28255, 255, 255, 0.25%29'/%3e%3c/svg%3e")}.form-range{appearance:none;background-color:#0000;width:100%;height:1.5rem;padding:0}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem #0d6efd40}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem #0d6efd40}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{appearance:none;background-color:#0d6efd;border:0;border-radius:1rem;width:1rem;height:1rem;margin-top:-.25rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-range::-webkit-slider-thumb{transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#b6d4fe}.form-range::-webkit-slider-runnable-track{color:#0000;cursor:pointer;background-color:var(--bs-secondary-bg);border-color:#0000;border-radius:1rem;width:100%;height:.5rem}.form-range::-moz-range-thumb{appearance:none;background-color:#0d6efd;border:0;border-radius:1rem;width:1rem;height:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-range::-moz-range-thumb{transition:none}}.form-range::-moz-range-thumb:active{background-color:#b6d4fe}.form-range::-moz-range-track{color:#0000;cursor:pointer;background-color:var(--bs-secondary-bg);border-color:#0000;border-radius:1rem;width:100%;height:.5rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:var(--bs-secondary-color)}.form-range:disabled::-moz-range-thumb{background-color:var(--bs-secondary-color)}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-control-plaintext,.form-floating>.form-select{height:calc(3.5rem + calc(var(--bs-border-width) * 2));min-height:calc(3.5rem + calc(var(--bs-border-width) * 2));line-height:1.25}.form-floating>label{z-index:2;max-width:100%;height:100%;color:rgba(var(--bs-body-color-rgb), .65);text-align:start;text-overflow:ellipsis;white-space:nowrap;pointer-events:none;border:var(--bs-border-width) solid transparent;transform-origin:0 0;padding:1rem .75rem;transition:opacity .1s ease-in-out,transform .1s ease-in-out;position:absolute;top:0;left:0;overflow:hidden}@media (prefers-reduced-motion:reduce){.form-floating>label{transition:none}}.form-floating>.form-control,.form-floating>.form-control-plaintext{padding:1rem .75rem}.form-floating>.form-control::placeholder,.form-floating>.form-control-plaintext::placeholder{color:#0000}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown),.form-floating>.form-control-plaintext:focus,.form-floating>.form-control-plaintext:not(:placeholder-shown),:-webkit-any(.form-floating>.form-control:-webkit-autofill,.form-floating>.form-control-plaintext:-webkit-autofill){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown),.form-floating>.form-control-plaintext:focus,.form-floating>.form-control-plaintext:not(:placeholder-shown),:is(.form-floating>.form-control:autofill,.form-floating>.form-control-plaintext:autofill){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem;padding-left:.75rem}.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-control-plaintext~label,.form-floating>.form-select~label{transform:scale(.85)translateY(-.5rem)translate(.15rem)}.form-floating>.form-control:-webkit-autofill~label{transform:scale(.85)translateY(-.5rem)translate(.15rem)}.form-floating>textarea:focus~label:after,.form-floating>textarea:not(:placeholder-shown)~label:after{z-index:-1;content:"";background-color:var(--bs-body-bg);border-radius:var(--bs-border-radius);height:1.5em;position:absolute;inset:1rem .375rem}.form-floating>textarea:disabled~label:after{background-color:var(--bs-secondary-bg)}.form-floating>.form-control-plaintext~label{border-width:var(--bs-border-width) 0}.form-floating>:disabled~label,.form-floating>.form-control:disabled~label{color:#6c757d}.input-group{flex-wrap:wrap;align-items:stretch;width:100%;display:flex;position:relative}.input-group>.form-control,.input-group>.form-select,.input-group>.form-floating{flex:auto;width:1%;min-width:0;position:relative}.input-group>.form-control:focus,.input-group>.form-select:focus,.input-group>.form-floating:focus-within{z-index:5}.input-group .btn{z-index:2;position:relative}.input-group .btn:focus{z-index:5}.input-group-text{color:var(--bs-body-color);text-align:center;white-space:nowrap;background-color:var(--bs-tertiary-bg);border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius);align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;display:flex}.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text,.input-group-lg>.btn{border-radius:var(--bs-border-radius-lg);padding:.5rem 1rem;font-size:1.25rem}.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text,.input-group-sm>.btn{border-radius:var(--bs-border-radius-sm);padding:.25rem .5rem;font-size:.875rem}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating),.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3),.input-group:not(.has-validation)>.form-floating:not(:last-child)>.form-control,.input-group:not(.has-validation)>.form-floating:not(:last-child)>.form-select,.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating),.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4),.input-group.has-validation>.form-floating:nth-last-child(n+3)>.form-control,.input-group.has-validation>.form-floating:nth-last-child(n+3)>.form-select{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:calc(-1 * var(--bs-border-width));border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.form-floating:not(:first-child)>.form-control,.input-group>.form-floating:not(:first-child)>.form-select{border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{width:100%;color:var(--bs-form-valid-color);margin-top:.25rem;font-size:.875em;display:none}.valid-tooltip{z-index:5;color:#fff;background-color:var(--bs-success);border-radius:var(--bs-border-radius);max-width:100%;margin-top:.1rem;padding:.25rem .5rem;font-size:.875rem;display:none;position:absolute;top:100%}.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip,.is-valid~.valid-feedback,.is-valid~.valid-tooltip{display:block}.was-validated .form-control:valid,.form-control.is-valid{border-color:var(--bs-form-valid-border-color);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1'/%3e%3c/svg%3e");background-position:right calc(.375em + .1875rem) center;background-repeat:no-repeat;background-size:calc(.75em + .375rem) calc(.75em + .375rem);padding-right:calc(1.5em + .75rem)}.was-validated .form-control:valid:focus,.form-control.is-valid:focus{border-color:var(--bs-form-valid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-success-rgb), .25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{background-position:right calc(.375em + .1875rem) top calc(.375em + .1875rem);padding-right:calc(1.5em + .75rem)}.was-validated .form-select:valid,.form-select.is-valid{border-color:var(--bs-form-valid-border-color)}.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"],.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"]{--bs-form-select-bg-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1'/%3e%3c/svg%3e");background-position:right .75rem center,right 2.25rem center;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem);padding-right:4.125rem}.was-validated .form-select:valid:focus,.form-select.is-valid:focus{border-color:var(--bs-form-valid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-success-rgb), .25)}.was-validated .form-control-color:valid,.form-control-color.is-valid{width:calc(1.5em + 3.75rem)}.was-validated .form-check-input:valid,.form-check-input.is-valid{border-color:var(--bs-form-valid-border-color)}.was-validated .form-check-input:valid:checked,.form-check-input.is-valid:checked{background-color:var(--bs-form-valid-color)}.was-validated .form-check-input:valid:focus,.form-check-input.is-valid:focus{box-shadow:0 0 0 .25rem rgba(var(--bs-success-rgb), .25)}.was-validated .form-check-input:valid~.form-check-label,.form-check-input.is-valid~.form-check-label{color:var(--bs-form-valid-color)}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.was-validated .input-group>.form-control:not(:focus):valid,.input-group>.form-control:not(:focus).is-valid,.was-validated .input-group>.form-select:not(:focus):valid,.input-group>.form-select:not(:focus).is-valid,.was-validated .input-group>.form-floating:not(:focus-within):valid,.input-group>.form-floating:not(:focus-within).is-valid{z-index:3}.invalid-feedback{width:100%;color:var(--bs-form-invalid-color);margin-top:.25rem;font-size:.875em;display:none}.invalid-tooltip{z-index:5;color:#fff;background-color:var(--bs-danger);border-radius:var(--bs-border-radius);max-width:100%;margin-top:.1rem;padding:.25rem .5rem;font-size:.875rem;display:none;position:absolute;top:100%}.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip,.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip{display:block}.was-validated .form-control:invalid,.form-control.is-invalid{border-color:var(--bs-form-invalid-border-color);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-position:right calc(.375em + .1875rem) center;background-repeat:no-repeat;background-size:calc(.75em + .375rem) calc(.75em + .375rem);padding-right:calc(1.5em + .75rem)}.was-validated .form-control:invalid:focus,.form-control.is-invalid:focus{border-color:var(--bs-form-invalid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-danger-rgb), .25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{background-position:right calc(.375em + .1875rem) top calc(.375em + .1875rem);padding-right:calc(1.5em + .75rem)}.was-validated .form-select:invalid,.form-select.is-invalid{border-color:var(--bs-form-invalid-border-color)}.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"],.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"]{--bs-form-select-bg-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-position:right .75rem center,right 2.25rem center;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem);padding-right:4.125rem}.was-validated .form-select:invalid:focus,.form-select.is-invalid:focus{border-color:var(--bs-form-invalid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-danger-rgb), .25)}.was-validated .form-control-color:invalid,.form-control-color.is-invalid{width:calc(1.5em + 3.75rem)}.was-validated .form-check-input:invalid,.form-check-input.is-invalid{border-color:var(--bs-form-invalid-border-color)}.was-validated .form-check-input:invalid:checked,.form-check-input.is-invalid:checked{background-color:var(--bs-form-invalid-color)}.was-validated .form-check-input:invalid:focus,.form-check-input.is-invalid:focus{box-shadow:0 0 0 .25rem rgba(var(--bs-danger-rgb), .25)}.was-validated .form-check-input:invalid~.form-check-label,.form-check-input.is-invalid~.form-check-label{color:var(--bs-form-invalid-color)}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.was-validated .input-group>.form-control:not(:focus):invalid,.input-group>.form-control:not(:focus).is-invalid,.was-validated .input-group>.form-select:not(:focus):invalid,.input-group>.form-select:not(:focus).is-invalid,.was-validated .input-group>.form-floating:not(:focus-within):invalid,.input-group>.form-floating:not(:focus-within).is-invalid{z-index:4}.btn{--bs-btn-padding-x:.75rem;--bs-btn-padding-y:.375rem;--bs-btn-font-family: ;--bs-btn-font-size:1rem;--bs-btn-font-weight:400;--bs-btn-line-height:1.5;--bs-btn-color:var(--bs-body-color);--bs-btn-bg:transparent;--bs-btn-border-width:var(--bs-border-width);--bs-btn-border-color:transparent;--bs-btn-border-radius:var(--bs-border-radius);--bs-btn-hover-border-color:transparent;--bs-btn-box-shadow:inset 0 1px 0 #ffffff26, 0 1px 1px #00000013;--bs-btn-disabled-opacity:.65;--bs-btn-focus-box-shadow:0 0 0 .25rem rgba(var(--bs-btn-focus-shadow-rgb), .5);padding:var(--bs-btn-padding-y) var(--bs-btn-padding-x);font-family:var(--bs-btn-font-family);font-size:var(--bs-btn-font-size);font-weight:var(--bs-btn-font-weight);line-height:var(--bs-btn-line-height);color:var(--bs-btn-color);text-align:center;vertical-align:middle;cursor:pointer;-webkit-user-select:none;user-select:none;border:var(--bs-btn-border-width) solid var(--bs-btn-border-color);border-radius:var(--bs-btn-border-radius);background-color:var(--bs-btn-bg);text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;display:inline-block}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:var(--bs-btn-hover-color);background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color)}.btn-check+.btn:hover{color:var(--bs-btn-color);background-color:var(--bs-btn-bg);border-color:var(--bs-btn-border-color)}.btn:focus-visible{color:var(--bs-btn-hover-color);background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color);box-shadow:var(--bs-btn-focus-box-shadow);outline:0}.btn-check:focus-visible+.btn{border-color:var(--bs-btn-hover-border-color);box-shadow:var(--bs-btn-focus-box-shadow);outline:0}.btn-check:checked+.btn,:not(.btn-check)+.btn:active,.btn:first-child:active,.btn.active,.btn.show{color:var(--bs-btn-active-color);background-color:var(--bs-btn-active-bg);border-color:var(--bs-btn-active-border-color)}.btn-check:checked+.btn:focus-visible,:not(.btn-check)+.btn:active:focus-visible,.btn:first-child:active:focus-visible,.btn.active:focus-visible,.btn.show:focus-visible,.btn-check:checked:focus-visible+.btn{box-shadow:var(--bs-btn-focus-box-shadow)}.btn:disabled,.btn.disabled,fieldset:disabled .btn{color:var(--bs-btn-disabled-color);pointer-events:none;background-color:var(--bs-btn-disabled-bg);border-color:var(--bs-btn-disabled-border-color);opacity:var(--bs-btn-disabled-opacity)}.btn-primary{--bs-btn-color:#fff;--bs-btn-bg:#0d6efd;--bs-btn-border-color:#0d6efd;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#0b5ed7;--bs-btn-hover-border-color:#0a58ca;--bs-btn-focus-shadow-rgb:49, 132, 253;--bs-btn-active-color:#fff;--bs-btn-active-bg:#0a58ca;--bs-btn-active-border-color:#0a53be;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#0d6efd;--bs-btn-disabled-border-color:#0d6efd}.btn-secondary{--bs-btn-color:#fff;--bs-btn-bg:#6c757d;--bs-btn-border-color:#6c757d;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#5c636a;--bs-btn-hover-border-color:#565e64;--bs-btn-focus-shadow-rgb:130, 138, 145;--bs-btn-active-color:#fff;--bs-btn-active-bg:#565e64;--bs-btn-active-border-color:#51585e;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#6c757d;--bs-btn-disabled-border-color:#6c757d}.btn-success{--bs-btn-color:#fff;--bs-btn-bg:#198754;--bs-btn-border-color:#198754;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#157347;--bs-btn-hover-border-color:#146c43;--bs-btn-focus-shadow-rgb:60, 153, 110;--bs-btn-active-color:#fff;--bs-btn-active-bg:#146c43;--bs-btn-active-border-color:#13653f;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#198754;--bs-btn-disabled-border-color:#198754}.btn-info{--bs-btn-color:#000;--bs-btn-bg:#0dcaf0;--bs-btn-border-color:#0dcaf0;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#31d2f2;--bs-btn-hover-border-color:#25cff2;--bs-btn-focus-shadow-rgb:11, 172, 204;--bs-btn-active-color:#000;--bs-btn-active-bg:#3dd5f3;--bs-btn-active-border-color:#25cff2;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#000;--bs-btn-disabled-bg:#0dcaf0;--bs-btn-disabled-border-color:#0dcaf0}.btn-warning{--bs-btn-color:#000;--bs-btn-bg:#ffc107;--bs-btn-border-color:#ffc107;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#ffca2c;--bs-btn-hover-border-color:#ffc720;--bs-btn-focus-shadow-rgb:217, 164, 6;--bs-btn-active-color:#000;--bs-btn-active-bg:#ffcd39;--bs-btn-active-border-color:#ffc720;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#000;--bs-btn-disabled-bg:#ffc107;--bs-btn-disabled-border-color:#ffc107}.btn-danger{--bs-btn-color:#fff;--bs-btn-bg:#dc3545;--bs-btn-border-color:#dc3545;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#bb2d3b;--bs-btn-hover-border-color:#b02a37;--bs-btn-focus-shadow-rgb:225, 83, 97;--bs-btn-active-color:#fff;--bs-btn-active-bg:#b02a37;--bs-btn-active-border-color:#a52834;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#dc3545;--bs-btn-disabled-border-color:#dc3545}.btn-light{--bs-btn-color:#000;--bs-btn-bg:#f8f9fa;--bs-btn-border-color:#f8f9fa;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#d3d4d5;--bs-btn-hover-border-color:#c6c7c8;--bs-btn-focus-shadow-rgb:211, 212, 213;--bs-btn-active-color:#000;--bs-btn-active-bg:#c6c7c8;--bs-btn-active-border-color:#babbbc;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#000;--bs-btn-disabled-bg:#f8f9fa;--bs-btn-disabled-border-color:#f8f9fa}.btn-dark{--bs-btn-color:#fff;--bs-btn-bg:#212529;--bs-btn-border-color:#212529;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#424649;--bs-btn-hover-border-color:#373b3e;--bs-btn-focus-shadow-rgb:66, 70, 73;--bs-btn-active-color:#fff;--bs-btn-active-bg:#4d5154;--bs-btn-active-border-color:#373b3e;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#212529;--bs-btn-disabled-border-color:#212529}.btn-outline-primary{--bs-btn-color:#0d6efd;--bs-btn-border-color:#0d6efd;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#0d6efd;--bs-btn-hover-border-color:#0d6efd;--bs-btn-focus-shadow-rgb:13, 110, 253;--bs-btn-active-color:#fff;--bs-btn-active-bg:#0d6efd;--bs-btn-active-border-color:#0d6efd;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#0d6efd;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#0d6efd;--bs-gradient:none}.btn-outline-secondary{--bs-btn-color:#6c757d;--bs-btn-border-color:#6c757d;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#6c757d;--bs-btn-hover-border-color:#6c757d;--bs-btn-focus-shadow-rgb:108, 117, 125;--bs-btn-active-color:#fff;--bs-btn-active-bg:#6c757d;--bs-btn-active-border-color:#6c757d;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#6c757d;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#6c757d;--bs-gradient:none}.btn-outline-success{--bs-btn-color:#198754;--bs-btn-border-color:#198754;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#198754;--bs-btn-hover-border-color:#198754;--bs-btn-focus-shadow-rgb:25, 135, 84;--bs-btn-active-color:#fff;--bs-btn-active-bg:#198754;--bs-btn-active-border-color:#198754;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#198754;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#198754;--bs-gradient:none}.btn-outline-info{--bs-btn-color:#0dcaf0;--bs-btn-border-color:#0dcaf0;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#0dcaf0;--bs-btn-hover-border-color:#0dcaf0;--bs-btn-focus-shadow-rgb:13, 202, 240;--bs-btn-active-color:#000;--bs-btn-active-bg:#0dcaf0;--bs-btn-active-border-color:#0dcaf0;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#0dcaf0;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#0dcaf0;--bs-gradient:none}.btn-outline-warning{--bs-btn-color:#ffc107;--bs-btn-border-color:#ffc107;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#ffc107;--bs-btn-hover-border-color:#ffc107;--bs-btn-focus-shadow-rgb:255, 193, 7;--bs-btn-active-color:#000;--bs-btn-active-bg:#ffc107;--bs-btn-active-border-color:#ffc107;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#ffc107;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#ffc107;--bs-gradient:none}.btn-outline-danger{--bs-btn-color:#dc3545;--bs-btn-border-color:#dc3545;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#dc3545;--bs-btn-hover-border-color:#dc3545;--bs-btn-focus-shadow-rgb:220, 53, 69;--bs-btn-active-color:#fff;--bs-btn-active-bg:#dc3545;--bs-btn-active-border-color:#dc3545;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#dc3545;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#dc3545;--bs-gradient:none}.btn-outline-light{--bs-btn-color:#f8f9fa;--bs-btn-border-color:#f8f9fa;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#f8f9fa;--bs-btn-hover-border-color:#f8f9fa;--bs-btn-focus-shadow-rgb:248, 249, 250;--bs-btn-active-color:#000;--bs-btn-active-bg:#f8f9fa;--bs-btn-active-border-color:#f8f9fa;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#f8f9fa;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#f8f9fa;--bs-gradient:none}.btn-outline-dark{--bs-btn-color:#212529;--bs-btn-border-color:#212529;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#212529;--bs-btn-hover-border-color:#212529;--bs-btn-focus-shadow-rgb:33, 37, 41;--bs-btn-active-color:#fff;--bs-btn-active-bg:#212529;--bs-btn-active-border-color:#212529;--bs-btn-active-shadow:inset 0 3px 5px #00000020;--bs-btn-disabled-color:#212529;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#212529;--bs-gradient:none}.btn-link{--bs-btn-font-weight:400;--bs-btn-color:var(--bs-link-color);--bs-btn-bg:transparent;--bs-btn-border-color:transparent;--bs-btn-hover-color:var(--bs-link-hover-color);--bs-btn-hover-border-color:transparent;--bs-btn-active-color:var(--bs-link-hover-color);--bs-btn-active-border-color:transparent;--bs-btn-disabled-color:#6c757d;--bs-btn-disabled-border-color:transparent;--bs-btn-box-shadow:0 0 0 #000;--bs-btn-focus-shadow-rgb:49, 132, 253;text-decoration:underline}.btn-link:focus-visible{color:var(--bs-btn-color)}.btn-link:hover{color:var(--bs-btn-hover-color)}.btn-lg,.btn-group-lg>.btn{--bs-btn-padding-y:.5rem;--bs-btn-padding-x:1rem;--bs-btn-font-size:1.25rem;--bs-btn-border-radius:var(--bs-border-radius-lg)}.btn-sm,.btn-group-sm>.btn{--bs-btn-padding-y:.25rem;--bs-btn-padding-x:.5rem;--bs-btn-font-size:.875rem;--bs-btn-border-radius:var(--bs-border-radius-sm)}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;transition:height .35s;overflow:hidden}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width .35s}@media (prefers-reduced-motion:reduce){.collapsing.collapse-horizontal{transition:none}}.dropup,.dropend,.dropdown,.dropstart,.dropup-center,.dropdown-center{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle:after{vertical-align:.255em;content:"";border:.3em solid #0000;border-top-color:currentColor;border-bottom:0;margin-left:.255em;display:inline-block}.dropdown-toggle:empty:after{margin-left:0}.dropdown-menu{--bs-dropdown-zindex:1000;--bs-dropdown-min-width:10rem;--bs-dropdown-padding-x:0;--bs-dropdown-padding-y:.5rem;--bs-dropdown-spacer:.125rem;--bs-dropdown-font-size:1rem;--bs-dropdown-color:var(--bs-body-color);--bs-dropdown-bg:var(--bs-body-bg);--bs-dropdown-border-color:var(--bs-border-color-translucent);--bs-dropdown-border-radius:var(--bs-border-radius);--bs-dropdown-border-width:var(--bs-border-width);--bs-dropdown-inner-border-radius:calc(var(--bs-border-radius) - var(--bs-border-width));--bs-dropdown-divider-bg:var(--bs-border-color-translucent);--bs-dropdown-divider-margin-y:.5rem;--bs-dropdown-box-shadow:var(--bs-box-shadow);--bs-dropdown-link-color:var(--bs-body-color);--bs-dropdown-link-hover-color:var(--bs-body-color);--bs-dropdown-link-hover-bg:var(--bs-tertiary-bg);--bs-dropdown-link-active-color:#fff;--bs-dropdown-link-active-bg:#0d6efd;--bs-dropdown-link-disabled-color:var(--bs-tertiary-color);--bs-dropdown-item-padding-x:1rem;--bs-dropdown-item-padding-y:.25rem;--bs-dropdown-header-color:#6c757d;--bs-dropdown-header-padding-x:1rem;--bs-dropdown-header-padding-y:.5rem;z-index:var(--bs-dropdown-zindex);min-width:var(--bs-dropdown-min-width);padding:var(--bs-dropdown-padding-y) var(--bs-dropdown-padding-x);font-size:var(--bs-dropdown-font-size);color:var(--bs-dropdown-color);text-align:left;background-color:var(--bs-dropdown-bg);border:var(--bs-dropdown-border-width) solid var(--bs-dropdown-border-color);border-radius:var(--bs-dropdown-border-radius);background-clip:padding-box;margin:0;list-style:none;display:none;position:absolute}.dropdown-menu[data-bs-popper]{margin-top:var(--bs-dropdown-spacer);top:100%;left:0}.dropdown-menu-start{--bs-position:start}.dropdown-menu-start[data-bs-popper]{left:0;right:auto}.dropdown-menu-end{--bs-position:end}.dropdown-menu-end[data-bs-popper]{left:auto;right:0}@media (width>=576px){.dropdown-menu-sm-start{--bs-position:start}.dropdown-menu-sm-start[data-bs-popper]{left:0;right:auto}.dropdown-menu-sm-end{--bs-position:end}.dropdown-menu-sm-end[data-bs-popper]{left:auto;right:0}}@media (width>=768px){.dropdown-menu-md-start{--bs-position:start}.dropdown-menu-md-start[data-bs-popper]{left:0;right:auto}.dropdown-menu-md-end{--bs-position:end}.dropdown-menu-md-end[data-bs-popper]{left:auto;right:0}}@media (width>=992px){.dropdown-menu-lg-start{--bs-position:start}.dropdown-menu-lg-start[data-bs-popper]{left:0;right:auto}.dropdown-menu-lg-end{--bs-position:end}.dropdown-menu-lg-end[data-bs-popper]{left:auto;right:0}}@media (width>=1200px){.dropdown-menu-xl-start{--bs-position:start}.dropdown-menu-xl-start[data-bs-popper]{left:0;right:auto}.dropdown-menu-xl-end{--bs-position:end}.dropdown-menu-xl-end[data-bs-popper]{left:auto;right:0}}@media (width>=1400px){.dropdown-menu-xxl-start{--bs-position:start}.dropdown-menu-xxl-start[data-bs-popper]{left:0;right:auto}.dropdown-menu-xxl-end{--bs-position:end}.dropdown-menu-xxl-end[data-bs-popper]{left:auto;right:0}}.dropup .dropdown-menu[data-bs-popper]{margin-top:0;margin-bottom:var(--bs-dropdown-spacer);top:auto;bottom:100%}.dropup .dropdown-toggle:after{vertical-align:.255em;content:"";border:.3em solid #0000;border-top:0;border-bottom-color:currentColor;margin-left:.255em;display:inline-block}.dropup .dropdown-toggle:empty:after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{margin-top:0;margin-left:var(--bs-dropdown-spacer);top:0;left:100%;right:auto}.dropend .dropdown-toggle:after{vertical-align:.255em;content:"";border:.3em solid #0000;border-left-color:currentColor;border-right:0;margin-left:.255em;display:inline-block}.dropend .dropdown-toggle:empty:after{margin-left:0}.dropend .dropdown-toggle:after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{margin-top:0;margin-right:var(--bs-dropdown-spacer);top:0;left:auto;right:100%}.dropstart .dropdown-toggle:after{vertical-align:.255em;content:"";margin-left:.255em;display:none}.dropstart .dropdown-toggle:before{vertical-align:.255em;content:"";border-top:.3em solid #0000;border-bottom:.3em solid #0000;border-right:.3em solid;margin-right:.255em;display:inline-block}.dropstart .dropdown-toggle:empty:after{margin-left:0}.dropstart .dropdown-toggle:before{vertical-align:0}.dropdown-divider{height:0;margin:var(--bs-dropdown-divider-margin-y) 0;border-top:1px solid var(--bs-dropdown-divider-bg);opacity:1;overflow:hidden}.dropdown-item{width:100%;padding:var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);clear:both;color:var(--bs-dropdown-link-color);text-align:inherit;white-space:nowrap;border-radius:var(--bs-dropdown-item-border-radius,0);background-color:#0000;border:0;font-weight:400;text-decoration:none;display:block}.dropdown-item:hover,.dropdown-item:focus{color:var(--bs-dropdown-link-hover-color);background-color:var(--bs-dropdown-link-hover-bg)}.dropdown-item.active,.dropdown-item:active{color:var(--bs-dropdown-link-active-color);background-color:var(--bs-dropdown-link-active-bg);text-decoration:none}.dropdown-item.disabled,.dropdown-item:disabled{color:var(--bs-dropdown-link-disabled-color);pointer-events:none;background-color:#0000}.dropdown-menu.show{display:block}.dropdown-header{padding:var(--bs-dropdown-header-padding-y) var(--bs-dropdown-header-padding-x);color:var(--bs-dropdown-header-color);white-space:nowrap;margin-bottom:0;font-size:.875rem;display:block}.dropdown-item-text{padding:var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);color:var(--bs-dropdown-link-color);display:block}.dropdown-menu-dark{--bs-dropdown-color:#dee2e6;--bs-dropdown-bg:#343a40;--bs-dropdown-border-color:var(--bs-border-color-translucent);--bs-dropdown-box-shadow: ;--bs-dropdown-link-color:#dee2e6;--bs-dropdown-link-hover-color:#fff;--bs-dropdown-divider-bg:var(--bs-border-color-translucent);--bs-dropdown-link-hover-bg:#ffffff26;--bs-dropdown-link-active-color:#fff;--bs-dropdown-link-active-bg:#0d6efd;--bs-dropdown-link-disabled-color:#adb5bd;--bs-dropdown-header-color:#adb5bd}.btn-group,.btn-group-vertical{vertical-align:middle;display:inline-flex;position:relative}.btn-group>.btn,.btn-group-vertical>.btn{flex:auto;position:relative}.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn:hover,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn.active{z-index:1}.btn-toolbar{flex-wrap:wrap;justify-content:flex-start;display:flex}.btn-toolbar .input-group{width:auto}.btn-group{border-radius:var(--bs-border-radius)}.btn-group>:not(.btn-check:first-child)+.btn,.btn-group>.btn-group:not(:first-child){margin-left:calc(-1 * var(--bs-border-width))}.btn-group>.btn:not(:last-child):not(.dropdown-toggle),.btn-group>.btn.dropdown-toggle-split:first-child,.btn-group>.btn-group:not(:last-child)>.btn{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn,.btn-group>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-left:.5625rem;padding-right:.5625rem}.dropdown-toggle-split:after,.dropup .dropdown-toggle-split:after,.dropend .dropdown-toggle-split:after{margin-left:0}.dropstart .dropdown-toggle-split:before{margin-right:0}.btn-sm+.dropdown-toggle-split,.btn-group-sm>.btn+.dropdown-toggle-split{padding-left:.375rem;padding-right:.375rem}.btn-lg+.dropdown-toggle-split,.btn-group-lg>.btn+.dropdown-toggle-split{padding-left:.75rem;padding-right:.75rem}.btn-group-vertical{flex-direction:column;justify-content:center;align-items:flex-start}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn:not(:first-child),.btn-group-vertical>.btn-group:not(:first-child){margin-top:calc(-1 * var(--bs-border-width))}.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle),.btn-group-vertical>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:nth-child(n+3),.btn-group-vertical>:not(.btn-check)+.btn,.btn-group-vertical>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{--bs-nav-link-padding-x:1rem;--bs-nav-link-padding-y:.5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color:var(--bs-link-color);--bs-nav-link-hover-color:var(--bs-link-hover-color);--bs-nav-link-disabled-color:var(--bs-secondary-color);flex-wrap:wrap;margin-bottom:0;padding-left:0;list-style:none;display:flex}.nav-link{padding:var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x);font-size:var(--bs-nav-link-font-size);font-weight:var(--bs-nav-link-font-weight);color:var(--bs-nav-link-color);background:0 0;border:0;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out;display:block}@media (prefers-reduced-motion:reduce){.nav-link{transition:none}}.nav-link:hover,.nav-link:focus{color:var(--bs-nav-link-hover-color)}.nav-link:focus-visible{outline:0;box-shadow:0 0 0 .25rem #0d6efd40}.nav-link.disabled,.nav-link:disabled{color:var(--bs-nav-link-disabled-color);pointer-events:none;cursor:default}.nav-tabs{--bs-nav-tabs-border-width:var(--bs-border-width);--bs-nav-tabs-border-color:var(--bs-border-color);--bs-nav-tabs-border-radius:var(--bs-border-radius);--bs-nav-tabs-link-hover-border-color:var(--bs-secondary-bg) var(--bs-secondary-bg) var(--bs-border-color);--bs-nav-tabs-link-active-color:var(--bs-emphasis-color);--bs-nav-tabs-link-active-bg:var(--bs-body-bg);--bs-nav-tabs-link-active-border-color:var(--bs-border-color) var(--bs-border-color) var(--bs-body-bg);border-bottom:var(--bs-nav-tabs-border-width) solid var(--bs-nav-tabs-border-color)}.nav-tabs .nav-link{margin-bottom:calc(-1 * var(--bs-nav-tabs-border-width));border:var(--bs-nav-tabs-border-width) solid transparent;border-top-left-radius:var(--bs-nav-tabs-border-radius);border-top-right-radius:var(--bs-nav-tabs-border-radius)}.nav-tabs .nav-link:hover,.nav-tabs .nav-link:focus{isolation:isolate;border-color:var(--bs-nav-tabs-link-hover-border-color)}.nav-tabs .nav-link.active,.nav-tabs .nav-item.show .nav-link{color:var(--bs-nav-tabs-link-active-color);background-color:var(--bs-nav-tabs-link-active-bg);border-color:var(--bs-nav-tabs-link-active-border-color)}.nav-tabs .dropdown-menu{margin-top:calc(-1 * var(--bs-nav-tabs-border-width));border-top-left-radius:0;border-top-right-radius:0}.nav-pills{--bs-nav-pills-border-radius:var(--bs-border-radius);--bs-nav-pills-link-active-color:#fff;--bs-nav-pills-link-active-bg:#0d6efd}.nav-pills .nav-link{border-radius:var(--bs-nav-pills-border-radius)}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:var(--bs-nav-pills-link-active-color);background-color:var(--bs-nav-pills-link-active-bg)}.nav-underline{--bs-nav-underline-gap:1rem;--bs-nav-underline-border-width:.125rem;--bs-nav-underline-link-active-color:var(--bs-emphasis-color);gap:var(--bs-nav-underline-gap)}.nav-underline .nav-link{border-bottom:var(--bs-nav-underline-border-width) solid transparent;padding-left:0;padding-right:0}.nav-underline .nav-link:hover,.nav-underline .nav-link:focus{border-bottom-color:currentColor}.nav-underline .nav-link.active,.nav-underline .show>.nav-link{color:var(--bs-nav-underline-link-active-color);border-bottom-color:currentColor;font-weight:700}.nav-fill>.nav-link,.nav-fill .nav-item{text-align:center;flex:auto}.nav-justified>.nav-link,.nav-justified .nav-item{text-align:center;flex-grow:1;flex-basis:0}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{--bs-navbar-padding-x:0;--bs-navbar-padding-y:.5rem;--bs-navbar-color:rgba(var(--bs-emphasis-color-rgb), .65);--bs-navbar-hover-color:rgba(var(--bs-emphasis-color-rgb), .8);--bs-navbar-disabled-color:rgba(var(--bs-emphasis-color-rgb), .3);--bs-navbar-active-color:rgba(var(--bs-emphasis-color-rgb), 1);--bs-navbar-brand-padding-y:.3125rem;--bs-navbar-brand-margin-end:1rem;--bs-navbar-brand-font-size:1.25rem;--bs-navbar-brand-color:rgba(var(--bs-emphasis-color-rgb), 1);--bs-navbar-brand-hover-color:rgba(var(--bs-emphasis-color-rgb), 1);--bs-navbar-nav-link-padding-x:.5rem;--bs-navbar-toggler-padding-y:.25rem;--bs-navbar-toggler-padding-x:.75rem;--bs-navbar-toggler-font-size:1.25rem;--bs-navbar-toggler-icon-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%2833, 37, 41, 0.75%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");--bs-navbar-toggler-border-color:rgba(var(--bs-emphasis-color-rgb), .15);--bs-navbar-toggler-border-radius:var(--bs-border-radius);--bs-navbar-toggler-focus-width:.25rem;--bs-navbar-toggler-transition:box-shadow .15s ease-in-out;padding:var(--bs-navbar-padding-y) var(--bs-navbar-padding-x);flex-wrap:wrap;justify-content:space-between;align-items:center;display:flex;position:relative}.navbar>.container,.navbar>.container-fluid,.navbar>.container-sm,.navbar>.container-md,.navbar>.container-lg,.navbar>.container-xl,.navbar>.container-xxl{flex-wrap:inherit;justify-content:space-between;align-items:center;display:flex}.navbar-brand{padding-top:var(--bs-navbar-brand-padding-y);padding-bottom:var(--bs-navbar-brand-padding-y);margin-right:var(--bs-navbar-brand-margin-end);font-size:var(--bs-navbar-brand-font-size);color:var(--bs-navbar-brand-color);white-space:nowrap;text-decoration:none}.navbar-brand:hover,.navbar-brand:focus{color:var(--bs-navbar-brand-hover-color)}.navbar-nav{--bs-nav-link-padding-x:0;--bs-nav-link-padding-y:.5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color:var(--bs-navbar-color);--bs-nav-link-hover-color:var(--bs-navbar-hover-color);--bs-nav-link-disabled-color:var(--bs-navbar-disabled-color);flex-direction:column;margin-bottom:0;padding-left:0;list-style:none;display:flex}.navbar-nav .nav-link.active,.navbar-nav .nav-link.show{color:var(--bs-navbar-active-color)}.navbar-nav .dropdown-menu{position:static}.navbar-text{color:var(--bs-navbar-color);padding-top:.5rem;padding-bottom:.5rem}.navbar-text a,.navbar-text a:hover,.navbar-text a:focus{color:var(--bs-navbar-active-color)}.navbar-collapse{flex-grow:1;flex-basis:100%;align-items:center}.navbar-toggler{padding:var(--bs-navbar-toggler-padding-y) var(--bs-navbar-toggler-padding-x);font-size:var(--bs-navbar-toggler-font-size);color:var(--bs-navbar-color);border:var(--bs-border-width) solid var(--bs-navbar-toggler-border-color);border-radius:var(--bs-navbar-toggler-border-radius);transition:var(--bs-navbar-toggler-transition);background-color:#0000;line-height:1}@media (prefers-reduced-motion:reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{box-shadow:0 0 0 var(--bs-navbar-toggler-focus-width);outline:0;text-decoration:none}.navbar-toggler-icon{vertical-align:middle;background-image:var(--bs-navbar-toggler-icon-bg);background-position:50%;background-repeat:no-repeat;background-size:100%;width:1.5em;height:1.5em;display:inline-block}.navbar-nav-scroll{max-height:var(--bs-scroll-height,75vh);overflow-y:auto}@media (width>=576px){.navbar-expand-sm{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{flex-basis:auto;display:flex!important}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas{z-index:auto;flex-grow:1;transition:none;position:static;visibility:visible!important;background-color:#0000!important;border:0!important;width:auto!important;height:auto!important;transform:none!important}.navbar-expand-sm .offcanvas .offcanvas-header{display:none}.navbar-expand-sm .offcanvas .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible}}@media (width>=768px){.navbar-expand-md{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{flex-basis:auto;display:flex!important}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas{z-index:auto;flex-grow:1;transition:none;position:static;visibility:visible!important;background-color:#0000!important;border:0!important;width:auto!important;height:auto!important;transform:none!important}.navbar-expand-md .offcanvas .offcanvas-header{display:none}.navbar-expand-md .offcanvas .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible}}@media (width>=992px){.navbar-expand-lg{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{flex-basis:auto;display:flex!important}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas{z-index:auto;flex-grow:1;transition:none;position:static;visibility:visible!important;background-color:#0000!important;border:0!important;width:auto!important;height:auto!important;transform:none!important}.navbar-expand-lg .offcanvas .offcanvas-header{display:none}.navbar-expand-lg .offcanvas .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible}}@media (width>=1200px){.navbar-expand-xl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{flex-basis:auto;display:flex!important}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas{z-index:auto;flex-grow:1;transition:none;position:static;visibility:visible!important;background-color:#0000!important;border:0!important;width:auto!important;height:auto!important;transform:none!important}.navbar-expand-xl .offcanvas .offcanvas-header{display:none}.navbar-expand-xl .offcanvas .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible}}@media (width>=1400px){.navbar-expand-xxl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{flex-basis:auto;display:flex!important}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas{z-index:auto;flex-grow:1;transition:none;position:static;visibility:visible!important;background-color:#0000!important;border:0!important;width:auto!important;height:auto!important;transform:none!important}.navbar-expand-xxl .offcanvas .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible}}.navbar-expand{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{flex-basis:auto;display:flex!important}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas{z-index:auto;flex-grow:1;transition:none;position:static;visibility:visible!important;background-color:#0000!important;border:0!important;width:auto!important;height:auto!important;transform:none!important}.navbar-expand .offcanvas .offcanvas-header{display:none}.navbar-expand .offcanvas .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible}.navbar-dark,.navbar[data-bs-theme=dark]{--bs-navbar-color:#ffffff8c;--bs-navbar-hover-color:#ffffffbf;--bs-navbar-disabled-color:#ffffff40;--bs-navbar-active-color:#fff;--bs-navbar-brand-color:#fff;--bs-navbar-brand-hover-color:#fff;--bs-navbar-toggler-border-color:#ffffff1a;--bs-navbar-toggler-icon-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}[data-bs-theme=dark] .navbar-toggler-icon{--bs-navbar-toggler-icon-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.card{--bs-card-spacer-y:1rem;--bs-card-spacer-x:1rem;--bs-card-title-spacer-y:.5rem;--bs-card-title-color: ;--bs-card-subtitle-color: ;--bs-card-border-width:var(--bs-border-width);--bs-card-border-color:var(--bs-border-color-translucent);--bs-card-border-radius:var(--bs-border-radius);--bs-card-box-shadow: ;--bs-card-inner-border-radius:calc(var(--bs-border-radius) - (var(--bs-border-width)));--bs-card-cap-padding-y:.5rem;--bs-card-cap-padding-x:1rem;--bs-card-cap-bg:rgba(var(--bs-body-color-rgb), .03);--bs-card-cap-color: ;--bs-card-height: ;--bs-card-color: ;--bs-card-bg:var(--bs-body-bg);--bs-card-img-overlay-padding:1rem;--bs-card-group-margin:.75rem;min-width:0;height:var(--bs-card-height);color:var(--bs-body-color);word-wrap:break-word;background-color:var(--bs-card-bg);border:var(--bs-card-border-width) solid var(--bs-card-border-color);border-radius:var(--bs-card-border-radius);background-clip:border-box;flex-direction:column;display:flex;position:relative}.card>hr{margin-left:0;margin-right:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-left-radius:var(--bs-card-inner-border-radius);border-top-right-radius:var(--bs-card-inner-border-radius);border-top-width:0}.card>.list-group:last-child{border-bottom-right-radius:var(--bs-card-inner-border-radius);border-bottom-left-radius:var(--bs-card-inner-border-radius);border-bottom-width:0}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{padding:var(--bs-card-spacer-y) var(--bs-card-spacer-x);color:var(--bs-card-color);flex:auto}.card-title{margin-bottom:var(--bs-card-title-spacer-y);color:var(--bs-card-title-color)}.card-subtitle{margin-top:calc(-.5 * var(--bs-card-title-spacer-y));color:var(--bs-card-subtitle-color);margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:var(--bs-card-spacer-x)}.card-header{padding:var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);color:var(--bs-card-cap-color);background-color:var(--bs-card-cap-bg);border-bottom:var(--bs-card-border-width) solid var(--bs-card-border-color);margin-bottom:0}.card-header:first-child{border-radius:var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius) 0 0}.card-footer{padding:var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);color:var(--bs-card-cap-color);background-color:var(--bs-card-cap-bg);border-top:var(--bs-card-border-width) solid var(--bs-card-border-color)}.card-footer:last-child{border-radius:0 0 var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius)}.card-header-tabs{margin-right:calc(-.5 * var(--bs-card-cap-padding-x));margin-bottom:calc(-1 * var(--bs-card-cap-padding-y));margin-left:calc(-.5 * var(--bs-card-cap-padding-x));border-bottom:0}.card-header-tabs .nav-link.active{background-color:var(--bs-card-bg);border-bottom-color:var(--bs-card-bg)}.card-header-pills{margin-right:calc(-.5 * var(--bs-card-cap-padding-x));margin-left:calc(-.5 * var(--bs-card-cap-padding-x))}.card-img-overlay{padding:var(--bs-card-img-overlay-padding);border-radius:var(--bs-card-inner-border-radius);position:absolute;inset:0}.card-img,.card-img-top,.card-img-bottom{width:100%}.card-img,.card-img-top{border-top-left-radius:var(--bs-card-inner-border-radius);border-top-right-radius:var(--bs-card-inner-border-radius)}.card-img,.card-img-bottom{border-bottom-right-radius:var(--bs-card-inner-border-radius);border-bottom-left-radius:var(--bs-card-inner-border-radius)}.card-group>.card{margin-bottom:var(--bs-card-group-margin)}@media (width>=576px){.card-group{flex-flow:wrap;display:flex}.card-group>.card{flex:1 0 0;margin-bottom:0}.card-group>.card+.card{border-left:0;margin-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child)>.card-img-top,.card-group>.card:not(:last-child)>.card-header{border-top-right-radius:0}.card-group>.card:not(:last-child)>.card-img-bottom,.card-group>.card:not(:last-child)>.card-footer{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child)>.card-img-top,.card-group>.card:not(:first-child)>.card-header{border-top-left-radius:0}.card-group>.card:not(:first-child)>.card-img-bottom,.card-group>.card:not(:first-child)>.card-footer{border-bottom-left-radius:0}}.accordion{--bs-accordion-color:var(--bs-body-color);--bs-accordion-bg:var(--bs-body-bg);--bs-accordion-transition:color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out, border-radius .15s ease;--bs-accordion-border-color:var(--bs-border-color);--bs-accordion-border-width:var(--bs-border-width);--bs-accordion-border-radius:var(--bs-border-radius);--bs-accordion-inner-border-radius:calc(var(--bs-border-radius) - (var(--bs-border-width)));--bs-accordion-btn-padding-x:1.25rem;--bs-accordion-btn-padding-y:1rem;--bs-accordion-btn-color:var(--bs-body-color);--bs-accordion-btn-bg:var(--bs-accordion-bg);--bs-accordion-btn-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23212529' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='m2 5 6 6 6-6'/%3e%3c/svg%3e");--bs-accordion-btn-icon-width:1.25rem;--bs-accordion-btn-icon-transform:rotate(-180deg);--bs-accordion-btn-icon-transition:transform .2s ease-in-out;--bs-accordion-btn-active-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='rgb%282.0392156863%, 17.2549019608%, 39.6862745098%%29' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='m2 5 6 6 6-6'/%3e%3c/svg%3e");--bs-accordion-btn-focus-box-shadow:0 0 0 .25rem #0d6efd40;--bs-accordion-body-padding-x:1.25rem;--bs-accordion-body-padding-y:1rem;--bs-accordion-active-color:var(--bs-primary-text-emphasis);--bs-accordion-active-bg:var(--bs-primary-bg-subtle)}.accordion-button{width:100%;padding:var(--bs-accordion-btn-padding-y) var(--bs-accordion-btn-padding-x);color:var(--bs-accordion-btn-color);text-align:left;background-color:var(--bs-accordion-btn-bg);overflow-anchor:none;transition:var(--bs-accordion-transition);border:0;border-radius:0;align-items:center;font-size:1rem;display:flex;position:relative}@media (prefers-reduced-motion:reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:var(--bs-accordion-active-color);background-color:var(--bs-accordion-active-bg);box-shadow:inset 0 calc(-1 * var(--bs-accordion-border-width)) 0 var(--bs-accordion-border-color)}.accordion-button:not(.collapsed):after{background-image:var(--bs-accordion-btn-active-icon);transform:var(--bs-accordion-btn-icon-transform)}.accordion-button:after{width:var(--bs-accordion-btn-icon-width);height:var(--bs-accordion-btn-icon-width);content:"";background-image:var(--bs-accordion-btn-icon);background-repeat:no-repeat;background-size:var(--bs-accordion-btn-icon-width);transition:var(--bs-accordion-btn-icon-transition);flex-shrink:0;margin-left:auto}@media (prefers-reduced-motion:reduce){.accordion-button:after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;box-shadow:var(--bs-accordion-btn-focus-box-shadow);outline:0}.accordion-header{margin-bottom:0}.accordion-item{color:var(--bs-accordion-color);background-color:var(--bs-accordion-bg);border:var(--bs-accordion-border-width) solid var(--bs-accordion-border-color)}.accordion-item:first-of-type{border-top-left-radius:var(--bs-accordion-border-radius);border-top-right-radius:var(--bs-accordion-border-radius)}.accordion-item:first-of-type>.accordion-header .accordion-button{border-top-left-radius:var(--bs-accordion-inner-border-radius);border-top-right-radius:var(--bs-accordion-inner-border-radius)}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-right-radius:var(--bs-accordion-border-radius);border-bottom-left-radius:var(--bs-accordion-border-radius)}.accordion-item:last-of-type>.accordion-header .accordion-button.collapsed{border-bottom-right-radius:var(--bs-accordion-inner-border-radius);border-bottom-left-radius:var(--bs-accordion-inner-border-radius)}.accordion-item:last-of-type>.accordion-collapse{border-bottom-right-radius:var(--bs-accordion-border-radius);border-bottom-left-radius:var(--bs-accordion-border-radius)}.accordion-body{padding:var(--bs-accordion-body-padding-y) var(--bs-accordion-body-padding-x)}.accordion-flush>.accordion-item{border-left:0;border-right:0;border-radius:0}.accordion-flush>.accordion-item:first-child{border-top:0}.accordion-flush>.accordion-item:last-child{border-bottom:0}.accordion-flush>.accordion-item>.accordion-collapse,.accordion-flush>.accordion-item>.accordion-header .accordion-button,.accordion-flush>.accordion-item>.accordion-header .accordion-button.collapsed{border-radius:0}[data-bs-theme=dark] .accordion-button:after{--bs-accordion-btn-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='rgb%2843.0588235294%, 65.8823529412%, 99.5294117647%%29'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708'/%3e%3c/svg%3e");--bs-accordion-btn-active-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='rgb%2843.0588235294%, 65.8823529412%, 99.5294117647%%29'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708'/%3e%3c/svg%3e")}.breadcrumb{--bs-breadcrumb-padding-x:0;--bs-breadcrumb-padding-y:0;--bs-breadcrumb-margin-bottom:1rem;--bs-breadcrumb-bg: ;--bs-breadcrumb-border-radius: ;--bs-breadcrumb-divider-color:var(--bs-secondary-color);--bs-breadcrumb-item-padding-x:.5rem;--bs-breadcrumb-item-active-color:var(--bs-secondary-color);padding:var(--bs-breadcrumb-padding-y) var(--bs-breadcrumb-padding-x);margin-bottom:var(--bs-breadcrumb-margin-bottom);font-size:var(--bs-breadcrumb-font-size);background-color:var(--bs-breadcrumb-bg);border-radius:var(--bs-breadcrumb-border-radius);flex-wrap:wrap;list-style:none;display:flex}.breadcrumb-item+.breadcrumb-item{padding-left:var(--bs-breadcrumb-item-padding-x)}.breadcrumb-item+.breadcrumb-item:before{float:left;padding-right:var(--bs-breadcrumb-item-padding-x);color:var(--bs-breadcrumb-divider-color);content:var(--bs-breadcrumb-divider,"/") }.breadcrumb-item.active{color:var(--bs-breadcrumb-item-active-color)}.pagination{--bs-pagination-padding-x:.75rem;--bs-pagination-padding-y:.375rem;--bs-pagination-font-size:1rem;--bs-pagination-color:var(--bs-link-color);--bs-pagination-bg:var(--bs-body-bg);--bs-pagination-border-width:var(--bs-border-width);--bs-pagination-border-color:var(--bs-border-color);--bs-pagination-border-radius:var(--bs-border-radius);--bs-pagination-hover-color:var(--bs-link-hover-color);--bs-pagination-hover-bg:var(--bs-tertiary-bg);--bs-pagination-hover-border-color:var(--bs-border-color);--bs-pagination-focus-color:var(--bs-link-hover-color);--bs-pagination-focus-bg:var(--bs-secondary-bg);--bs-pagination-focus-box-shadow:0 0 0 .25rem #0d6efd40;--bs-pagination-active-color:#fff;--bs-pagination-active-bg:#0d6efd;--bs-pagination-active-border-color:#0d6efd;--bs-pagination-disabled-color:var(--bs-secondary-color);--bs-pagination-disabled-bg:var(--bs-secondary-bg);--bs-pagination-disabled-border-color:var(--bs-border-color);padding-left:0;list-style:none;display:flex}.page-link{padding:var(--bs-pagination-padding-y) var(--bs-pagination-padding-x);font-size:var(--bs-pagination-font-size);color:var(--bs-pagination-color);background-color:var(--bs-pagination-bg);border:var(--bs-pagination-border-width) solid var(--bs-pagination-border-color);text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;display:block;position:relative}@media (prefers-reduced-motion:reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:var(--bs-pagination-hover-color);background-color:var(--bs-pagination-hover-bg);border-color:var(--bs-pagination-hover-border-color)}.page-link:focus{z-index:3;color:var(--bs-pagination-focus-color);background-color:var(--bs-pagination-focus-bg);box-shadow:var(--bs-pagination-focus-box-shadow);outline:0}.page-link.active,.active>.page-link{z-index:3;color:var(--bs-pagination-active-color);background-color:var(--bs-pagination-active-bg);border-color:var(--bs-pagination-active-border-color)}.page-link.disabled,.disabled>.page-link{color:var(--bs-pagination-disabled-color);pointer-events:none;background-color:var(--bs-pagination-disabled-bg);border-color:var(--bs-pagination-disabled-border-color)}.page-item:not(:first-child) .page-link{margin-left:calc(-1 * var(--bs-border-width))}.page-item:first-child .page-link{border-top-left-radius:var(--bs-pagination-border-radius);border-bottom-left-radius:var(--bs-pagination-border-radius)}.page-item:last-child .page-link{border-top-right-radius:var(--bs-pagination-border-radius);border-bottom-right-radius:var(--bs-pagination-border-radius)}.pagination-lg{--bs-pagination-padding-x:1.5rem;--bs-pagination-padding-y:.75rem;--bs-pagination-font-size:1.25rem;--bs-pagination-border-radius:var(--bs-border-radius-lg)}.pagination-sm{--bs-pagination-padding-x:.5rem;--bs-pagination-padding-y:.25rem;--bs-pagination-font-size:.875rem;--bs-pagination-border-radius:var(--bs-border-radius-sm)}.badge{--bs-badge-padding-x:.65em;--bs-badge-padding-y:.35em;--bs-badge-font-size:.75em;--bs-badge-font-weight:700;--bs-badge-color:#fff;--bs-badge-border-radius:var(--bs-border-radius);padding:var(--bs-badge-padding-y) var(--bs-badge-padding-x);font-size:var(--bs-badge-font-size);font-weight:var(--bs-badge-font-weight);color:var(--bs-badge-color);text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:var(--bs-badge-border-radius);line-height:1;display:inline-block}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{--bs-alert-bg:transparent;--bs-alert-padding-x:1rem;--bs-alert-padding-y:1rem;--bs-alert-margin-bottom:1rem;--bs-alert-color:inherit;--bs-alert-border-color:transparent;--bs-alert-border:var(--bs-border-width) solid var(--bs-alert-border-color);--bs-alert-border-radius:var(--bs-border-radius);--bs-alert-link-color:inherit;padding:var(--bs-alert-padding-y) var(--bs-alert-padding-x);margin-bottom:var(--bs-alert-margin-bottom);color:var(--bs-alert-color);background-color:var(--bs-alert-bg);border:var(--bs-alert-border);border-radius:var(--bs-alert-border-radius);position:relative}.alert-heading{color:inherit}.alert-link{color:var(--bs-alert-link-color);font-weight:700}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{z-index:2;padding:1.25rem 1rem;position:absolute;top:0;right:0}.alert-primary{--bs-alert-color:var(--bs-primary-text-emphasis);--bs-alert-bg:var(--bs-primary-bg-subtle);--bs-alert-border-color:var(--bs-primary-border-subtle);--bs-alert-link-color:var(--bs-primary-text-emphasis)}.alert-secondary{--bs-alert-color:var(--bs-secondary-text-emphasis);--bs-alert-bg:var(--bs-secondary-bg-subtle);--bs-alert-border-color:var(--bs-secondary-border-subtle);--bs-alert-link-color:var(--bs-secondary-text-emphasis)}.alert-success{--bs-alert-color:var(--bs-success-text-emphasis);--bs-alert-bg:var(--bs-success-bg-subtle);--bs-alert-border-color:var(--bs-success-border-subtle);--bs-alert-link-color:var(--bs-success-text-emphasis)}.alert-info{--bs-alert-color:var(--bs-info-text-emphasis);--bs-alert-bg:var(--bs-info-bg-subtle);--bs-alert-border-color:var(--bs-info-border-subtle);--bs-alert-link-color:var(--bs-info-text-emphasis)}.alert-warning{--bs-alert-color:var(--bs-warning-text-emphasis);--bs-alert-bg:var(--bs-warning-bg-subtle);--bs-alert-border-color:var(--bs-warning-border-subtle);--bs-alert-link-color:var(--bs-warning-text-emphasis)}.alert-danger{--bs-alert-color:var(--bs-danger-text-emphasis);--bs-alert-bg:var(--bs-danger-bg-subtle);--bs-alert-border-color:var(--bs-danger-border-subtle);--bs-alert-link-color:var(--bs-danger-text-emphasis)}.alert-light{--bs-alert-color:var(--bs-light-text-emphasis);--bs-alert-bg:var(--bs-light-bg-subtle);--bs-alert-border-color:var(--bs-light-border-subtle);--bs-alert-link-color:var(--bs-light-text-emphasis)}.alert-dark{--bs-alert-color:var(--bs-dark-text-emphasis);--bs-alert-bg:var(--bs-dark-bg-subtle);--bs-alert-border-color:var(--bs-dark-border-subtle);--bs-alert-link-color:var(--bs-dark-text-emphasis)}@keyframes progress-bar-stripes{0%{background-position-x:var(--bs-progress-height)}}.progress,.progress-stacked{--bs-progress-height:1rem;--bs-progress-font-size:.75rem;--bs-progress-bg:var(--bs-secondary-bg);--bs-progress-border-radius:var(--bs-border-radius);--bs-progress-box-shadow:var(--bs-box-shadow-inset);--bs-progress-bar-color:#fff;--bs-progress-bar-bg:#0d6efd;--bs-progress-bar-transition:width .6s ease;height:var(--bs-progress-height);font-size:var(--bs-progress-font-size);background-color:var(--bs-progress-bg);border-radius:var(--bs-progress-border-radius);display:flex;overflow:hidden}.progress-bar{color:var(--bs-progress-bar-color);text-align:center;white-space:nowrap;background-color:var(--bs-progress-bar-bg);transition:var(--bs-progress-bar-transition);flex-direction:column;justify-content:center;display:flex;overflow:hidden}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,#ffffff26 25%,#0000 25% 50%,#ffffff26 50% 75%,#0000 75%,#0000);background-size:var(--bs-progress-height) var(--bs-progress-height)}.progress-stacked>.progress{overflow:visible}.progress-stacked>.progress>.progress-bar{width:100%}.progress-bar-animated{animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion:reduce){.progress-bar-animated{animation:none}}.list-group{--bs-list-group-color:var(--bs-body-color);--bs-list-group-bg:var(--bs-body-bg);--bs-list-group-border-color:var(--bs-border-color);--bs-list-group-border-width:var(--bs-border-width);--bs-list-group-border-radius:var(--bs-border-radius);--bs-list-group-item-padding-x:1rem;--bs-list-group-item-padding-y:.5rem;--bs-list-group-action-color:var(--bs-secondary-color);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-tertiary-bg);--bs-list-group-action-active-color:var(--bs-body-color);--bs-list-group-action-active-bg:var(--bs-secondary-bg);--bs-list-group-disabled-color:var(--bs-secondary-color);--bs-list-group-disabled-bg:var(--bs-body-bg);--bs-list-group-active-color:#fff;--bs-list-group-active-bg:#0d6efd;--bs-list-group-active-border-color:#0d6efd;border-radius:var(--bs-list-group-border-radius);flex-direction:column;margin-bottom:0;padding-left:0;display:flex}.list-group-numbered{counter-reset:section;list-style-type:none}.list-group-numbered>.list-group-item:before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item{padding:var(--bs-list-group-item-padding-y) var(--bs-list-group-item-padding-x);color:var(--bs-list-group-color);background-color:var(--bs-list-group-bg);border:var(--bs-list-group-border-width) solid var(--bs-list-group-border-color);text-decoration:none;display:block;position:relative}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:var(--bs-list-group-disabled-color);pointer-events:none;background-color:var(--bs-list-group-disabled-bg)}.list-group-item.active{z-index:2;color:var(--bs-list-group-active-color);background-color:var(--bs-list-group-active-bg);border-color:var(--bs-list-group-active-border-color)}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:calc(-1 * var(--bs-list-group-border-width));border-top-width:var(--bs-list-group-border-width)}.list-group-item-action{width:100%;color:var(--bs-list-group-action-color);text-align:inherit}.list-group-item-action:not(.active):hover,.list-group-item-action:not(.active):focus{z-index:1;color:var(--bs-list-group-action-hover-color);background-color:var(--bs-list-group-action-hover-bg);text-decoration:none}.list-group-item-action:not(.active):active{color:var(--bs-list-group-action-active-color);background-color:var(--bs-list-group-action-active-bg)}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}@media (width>=576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (width>=768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (width>=992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (width>=1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (width>=1400px){.list-group-horizontal-xxl{flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 var(--bs-list-group-border-width)}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{--bs-list-group-color:var(--bs-primary-text-emphasis);--bs-list-group-bg:var(--bs-primary-bg-subtle);--bs-list-group-border-color:var(--bs-primary-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-primary-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-primary-border-subtle);--bs-list-group-active-color:var(--bs-primary-bg-subtle);--bs-list-group-active-bg:var(--bs-primary-text-emphasis);--bs-list-group-active-border-color:var(--bs-primary-text-emphasis)}.list-group-item-secondary{--bs-list-group-color:var(--bs-secondary-text-emphasis);--bs-list-group-bg:var(--bs-secondary-bg-subtle);--bs-list-group-border-color:var(--bs-secondary-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-secondary-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-secondary-border-subtle);--bs-list-group-active-color:var(--bs-secondary-bg-subtle);--bs-list-group-active-bg:var(--bs-secondary-text-emphasis);--bs-list-group-active-border-color:var(--bs-secondary-text-emphasis)}.list-group-item-success{--bs-list-group-color:var(--bs-success-text-emphasis);--bs-list-group-bg:var(--bs-success-bg-subtle);--bs-list-group-border-color:var(--bs-success-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-success-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-success-border-subtle);--bs-list-group-active-color:var(--bs-success-bg-subtle);--bs-list-group-active-bg:var(--bs-success-text-emphasis);--bs-list-group-active-border-color:var(--bs-success-text-emphasis)}.list-group-item-info{--bs-list-group-color:var(--bs-info-text-emphasis);--bs-list-group-bg:var(--bs-info-bg-subtle);--bs-list-group-border-color:var(--bs-info-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-info-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-info-border-subtle);--bs-list-group-active-color:var(--bs-info-bg-subtle);--bs-list-group-active-bg:var(--bs-info-text-emphasis);--bs-list-group-active-border-color:var(--bs-info-text-emphasis)}.list-group-item-warning{--bs-list-group-color:var(--bs-warning-text-emphasis);--bs-list-group-bg:var(--bs-warning-bg-subtle);--bs-list-group-border-color:var(--bs-warning-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-warning-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-warning-border-subtle);--bs-list-group-active-color:var(--bs-warning-bg-subtle);--bs-list-group-active-bg:var(--bs-warning-text-emphasis);--bs-list-group-active-border-color:var(--bs-warning-text-emphasis)}.list-group-item-danger{--bs-list-group-color:var(--bs-danger-text-emphasis);--bs-list-group-bg:var(--bs-danger-bg-subtle);--bs-list-group-border-color:var(--bs-danger-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-danger-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-danger-border-subtle);--bs-list-group-active-color:var(--bs-danger-bg-subtle);--bs-list-group-active-bg:var(--bs-danger-text-emphasis);--bs-list-group-active-border-color:var(--bs-danger-text-emphasis)}.list-group-item-light{--bs-list-group-color:var(--bs-light-text-emphasis);--bs-list-group-bg:var(--bs-light-bg-subtle);--bs-list-group-border-color:var(--bs-light-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-light-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-light-border-subtle);--bs-list-group-active-color:var(--bs-light-bg-subtle);--bs-list-group-active-bg:var(--bs-light-text-emphasis);--bs-list-group-active-border-color:var(--bs-light-text-emphasis)}.list-group-item-dark{--bs-list-group-color:var(--bs-dark-text-emphasis);--bs-list-group-bg:var(--bs-dark-bg-subtle);--bs-list-group-border-color:var(--bs-dark-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-dark-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-dark-border-subtle);--bs-list-group-active-color:var(--bs-dark-bg-subtle);--bs-list-group-active-bg:var(--bs-dark-text-emphasis);--bs-list-group-active-border-color:var(--bs-dark-text-emphasis)}.btn-close{--bs-btn-close-color:#000;--bs-btn-close-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414'/%3e%3c/svg%3e");--bs-btn-close-opacity:.5;--bs-btn-close-hover-opacity:.75;--bs-btn-close-focus-shadow:0 0 0 .25rem #0d6efd40;--bs-btn-close-focus-opacity:1;--bs-btn-close-disabled-opacity:.25;box-sizing:content-box;width:1em;height:1em;color:var(--bs-btn-close-color);background:transparent var(--bs-btn-close-bg) center/1em auto no-repeat;filter:var(--bs-btn-close-filter);opacity:var(--bs-btn-close-opacity);border:0;border-radius:.375rem;padding:.25em}.btn-close:hover{color:var(--bs-btn-close-color);opacity:var(--bs-btn-close-hover-opacity);text-decoration:none}.btn-close:focus{box-shadow:var(--bs-btn-close-focus-shadow);opacity:var(--bs-btn-close-focus-opacity);outline:0}.btn-close:disabled,.btn-close.disabled{pointer-events:none;-webkit-user-select:none;user-select:none;opacity:var(--bs-btn-close-disabled-opacity)}.btn-close-white{--bs-btn-close-filter:invert(1) grayscale(100%) brightness(200%)}:root,[data-bs-theme=light]{--bs-btn-close-filter: }[data-bs-theme=dark]{--bs-btn-close-filter:invert(1) grayscale(100%) brightness(200%)}.toast{--bs-toast-zindex:1090;--bs-toast-padding-x:.75rem;--bs-toast-padding-y:.5rem;--bs-toast-spacing:1.5rem;--bs-toast-max-width:350px;--bs-toast-font-size:.875rem;--bs-toast-color: ;--bs-toast-bg:rgba(var(--bs-body-bg-rgb), .85);--bs-toast-border-width:var(--bs-border-width);--bs-toast-border-color:var(--bs-border-color-translucent);--bs-toast-border-radius:var(--bs-border-radius);--bs-toast-box-shadow:var(--bs-box-shadow);--bs-toast-header-color:var(--bs-secondary-color);--bs-toast-header-bg:rgba(var(--bs-body-bg-rgb), .85);--bs-toast-header-border-color:var(--bs-border-color-translucent);width:var(--bs-toast-max-width);max-width:100%;font-size:var(--bs-toast-font-size);color:var(--bs-toast-color);pointer-events:auto;background-color:var(--bs-toast-bg);border:var(--bs-toast-border-width) solid var(--bs-toast-border-color);box-shadow:var(--bs-toast-box-shadow);border-radius:var(--bs-toast-border-radius);background-clip:padding-box}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{--bs-toast-zindex:1090;z-index:var(--bs-toast-zindex);pointer-events:none;width:max-content;max-width:100%;position:absolute}.toast-container>:not(:last-child){margin-bottom:var(--bs-toast-spacing)}.toast-header{padding:var(--bs-toast-padding-y) var(--bs-toast-padding-x);color:var(--bs-toast-header-color);background-color:var(--bs-toast-header-bg);border-bottom:var(--bs-toast-border-width) solid var(--bs-toast-header-border-color);border-top-left-radius:calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width));border-top-right-radius:calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width));background-clip:padding-box;align-items:center;display:flex}.toast-header .btn-close{margin-right:calc(-.5 * var(--bs-toast-padding-x));margin-left:var(--bs-toast-padding-x)}.toast-body{padding:var(--bs-toast-padding-x);word-wrap:break-word}.modal{--bs-modal-zindex:1055;--bs-modal-width:500px;--bs-modal-padding:1rem;--bs-modal-margin:.5rem;--bs-modal-color:var(--bs-body-color);--bs-modal-bg:var(--bs-body-bg);--bs-modal-border-color:var(--bs-border-color-translucent);--bs-modal-border-width:var(--bs-border-width);--bs-modal-border-radius:var(--bs-border-radius-lg);--bs-modal-box-shadow:var(--bs-box-shadow-sm);--bs-modal-inner-border-radius:calc(var(--bs-border-radius-lg) - (var(--bs-border-width)));--bs-modal-header-padding-x:1rem;--bs-modal-header-padding-y:1rem;--bs-modal-header-padding:1rem 1rem;--bs-modal-header-border-color:var(--bs-border-color);--bs-modal-header-border-width:var(--bs-border-width);--bs-modal-title-line-height:1.5;--bs-modal-footer-gap:.5rem;--bs-modal-footer-bg: ;--bs-modal-footer-border-color:var(--bs-border-color);--bs-modal-footer-border-width:var(--bs-border-width);z-index:var(--bs-modal-zindex);outline:0;width:100%;height:100%;display:none;position:fixed;top:0;left:0;overflow:hidden auto}.modal-dialog{width:auto;margin:var(--bs-modal-margin);pointer-events:none;position:relative}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translateY(-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - var(--bs-modal-margin) * 2)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{min-height:calc(100% - var(--bs-modal-margin) * 2);align-items:center;display:flex}.modal-content{width:100%;color:var(--bs-modal-color);pointer-events:auto;background-color:var(--bs-modal-bg);border:var(--bs-modal-border-width) solid var(--bs-modal-border-color);border-radius:var(--bs-modal-border-radius);background-clip:padding-box;outline:0;flex-direction:column;display:flex;position:relative}.modal-backdrop{--bs-backdrop-zindex:1050;--bs-backdrop-bg:#000;--bs-backdrop-opacity:.5;z-index:var(--bs-backdrop-zindex);background-color:var(--bs-backdrop-bg);width:100vw;height:100vh;position:fixed;top:0;left:0}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:var(--bs-backdrop-opacity)}.modal-header{padding:var(--bs-modal-header-padding);border-bottom:var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color);border-top-left-radius:var(--bs-modal-inner-border-radius);border-top-right-radius:var(--bs-modal-inner-border-radius);flex-shrink:0;align-items:center;display:flex}.modal-header .btn-close{padding:calc(var(--bs-modal-header-padding-y) * .5) calc(var(--bs-modal-header-padding-x) * .5);margin-top:calc(-.5 * var(--bs-modal-header-padding-y));margin-right:calc(-.5 * var(--bs-modal-header-padding-x));margin-bottom:calc(-.5 * var(--bs-modal-header-padding-y));margin-left:auto}.modal-title{line-height:var(--bs-modal-title-line-height);margin-bottom:0}.modal-body{padding:var(--bs-modal-padding);flex:auto;position:relative}.modal-footer{padding:calc(var(--bs-modal-padding) - var(--bs-modal-footer-gap) * .5);background-color:var(--bs-modal-footer-bg);border-top:var(--bs-modal-footer-border-width) solid var(--bs-modal-footer-border-color);border-bottom-right-radius:var(--bs-modal-inner-border-radius);border-bottom-left-radius:var(--bs-modal-inner-border-radius);flex-wrap:wrap;flex-shrink:0;justify-content:flex-end;align-items:center;display:flex}.modal-footer>*{margin:calc(var(--bs-modal-footer-gap) * .5)}@media (width>=576px){.modal{--bs-modal-margin:1.75rem;--bs-modal-box-shadow:var(--bs-box-shadow)}.modal-dialog{max-width:var(--bs-modal-width);margin-left:auto;margin-right:auto}.modal-sm{--bs-modal-width:300px}}@media (width>=992px){.modal-lg,.modal-xl{--bs-modal-width:800px}}@media (width>=1200px){.modal-xl{--bs-modal-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{border:0;border-radius:0;height:100%}.modal-fullscreen .modal-header,.modal-fullscreen .modal-footer{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}@media (width<=575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{border:0;border-radius:0;height:100%}.modal-fullscreen-sm-down .modal-header,.modal-fullscreen-sm-down .modal-footer{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}}@media (width<=767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{border:0;border-radius:0;height:100%}.modal-fullscreen-md-down .modal-header,.modal-fullscreen-md-down .modal-footer{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}}@media (width<=991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{border:0;border-radius:0;height:100%}.modal-fullscreen-lg-down .modal-header,.modal-fullscreen-lg-down .modal-footer{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}}@media (width<=1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{border:0;border-radius:0;height:100%}.modal-fullscreen-xl-down .modal-header,.modal-fullscreen-xl-down .modal-footer{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}}@media (width<=1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{border:0;border-radius:0;height:100%}.modal-fullscreen-xxl-down .modal-header,.modal-fullscreen-xxl-down .modal-footer{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}}.tooltip{--bs-tooltip-zindex:1080;--bs-tooltip-max-width:200px;--bs-tooltip-padding-x:.5rem;--bs-tooltip-padding-y:.25rem;--bs-tooltip-margin: ;--bs-tooltip-font-size:.875rem;--bs-tooltip-color:var(--bs-body-bg);--bs-tooltip-bg:var(--bs-emphasis-color);--bs-tooltip-border-radius:var(--bs-border-radius);--bs-tooltip-opacity:.9;--bs-tooltip-arrow-width:.8rem;--bs-tooltip-arrow-height:.4rem;z-index:var(--bs-tooltip-zindex);margin:var(--bs-tooltip-margin);font-family:var(--bs-font-sans-serif);text-align:left;text-align:start;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;white-space:normal;word-spacing:normal;line-break:auto;font-style:normal;font-weight:400;line-height:1.5;font-size:var(--bs-tooltip-font-size);word-wrap:break-word;opacity:0;text-decoration:none;display:block}.tooltip.show{opacity:var(--bs-tooltip-opacity)}.tooltip .tooltip-arrow{width:var(--bs-tooltip-arrow-width);height:var(--bs-tooltip-arrow-height);display:block}.tooltip .tooltip-arrow:before{content:"";border-style:solid;border-color:#0000;position:absolute}.bs-tooltip-top .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow{bottom:calc(-1 * var(--bs-tooltip-arrow-height))}.bs-tooltip-top .tooltip-arrow:before,.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow:before{border-width:var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * .5) 0;border-top-color:var(--bs-tooltip-bg);top:-1px}.bs-tooltip-end .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow{left:calc(-1 * var(--bs-tooltip-arrow-height));width:var(--bs-tooltip-arrow-height);height:var(--bs-tooltip-arrow-width)}.bs-tooltip-end .tooltip-arrow:before,.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow:before{border-width:calc(var(--bs-tooltip-arrow-width) * .5) var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * .5) 0;border-right-color:var(--bs-tooltip-bg);right:-1px}.bs-tooltip-bottom .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow{top:calc(-1 * var(--bs-tooltip-arrow-height))}.bs-tooltip-bottom .tooltip-arrow:before,.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow:before{border-width:0 calc(var(--bs-tooltip-arrow-width) * .5) var(--bs-tooltip-arrow-height);border-bottom-color:var(--bs-tooltip-bg);bottom:-1px}.bs-tooltip-start .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow{right:calc(-1 * var(--bs-tooltip-arrow-height));width:var(--bs-tooltip-arrow-height);height:var(--bs-tooltip-arrow-width)}.bs-tooltip-start .tooltip-arrow:before,.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow:before{border-width:calc(var(--bs-tooltip-arrow-width) * .5) 0 calc(var(--bs-tooltip-arrow-width) * .5) var(--bs-tooltip-arrow-height);border-left-color:var(--bs-tooltip-bg);left:-1px}.tooltip-inner{max-width:var(--bs-tooltip-max-width);padding:var(--bs-tooltip-padding-y) var(--bs-tooltip-padding-x);color:var(--bs-tooltip-color);text-align:center;background-color:var(--bs-tooltip-bg);border-radius:var(--bs-tooltip-border-radius)}.popover{--bs-popover-zindex:1070;--bs-popover-max-width:276px;--bs-popover-font-size:.875rem;--bs-popover-bg:var(--bs-body-bg);--bs-popover-border-width:var(--bs-border-width);--bs-popover-border-color:var(--bs-border-color-translucent);--bs-popover-border-radius:var(--bs-border-radius-lg);--bs-popover-inner-border-radius:calc(var(--bs-border-radius-lg) - var(--bs-border-width));--bs-popover-box-shadow:var(--bs-box-shadow);--bs-popover-header-padding-x:1rem;--bs-popover-header-padding-y:.5rem;--bs-popover-header-font-size:1rem;--bs-popover-header-color:inherit;--bs-popover-header-bg:var(--bs-secondary-bg);--bs-popover-body-padding-x:1rem;--bs-popover-body-padding-y:1rem;--bs-popover-body-color:var(--bs-body-color);--bs-popover-arrow-width:1rem;--bs-popover-arrow-height:.5rem;--bs-popover-arrow-border:var(--bs-popover-border-color);z-index:var(--bs-popover-zindex);max-width:var(--bs-popover-max-width);font-family:var(--bs-font-sans-serif);text-align:left;text-align:start;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;white-space:normal;word-spacing:normal;line-break:auto;font-style:normal;font-weight:400;line-height:1.5;font-size:var(--bs-popover-font-size);word-wrap:break-word;background-color:var(--bs-popover-bg);border:var(--bs-popover-border-width) solid var(--bs-popover-border-color);border-radius:var(--bs-popover-border-radius);background-clip:padding-box;text-decoration:none;display:block}.popover .popover-arrow{width:var(--bs-popover-arrow-width);height:var(--bs-popover-arrow-height);display:block}.popover .popover-arrow:before,.popover .popover-arrow:after{content:"";border:0 solid #0000;display:block;position:absolute}.bs-popover-top>.popover-arrow,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow{bottom:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width))}.bs-popover-top>.popover-arrow:before,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow:before,.bs-popover-top>.popover-arrow:after,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow:after{border-width:var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * .5) 0}.bs-popover-top>.popover-arrow:before,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow:before{border-top-color:var(--bs-popover-arrow-border);bottom:0}.bs-popover-top>.popover-arrow:after,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow:after{bottom:var(--bs-popover-border-width);border-top-color:var(--bs-popover-bg)}.bs-popover-end>.popover-arrow,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow{left:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));width:var(--bs-popover-arrow-height);height:var(--bs-popover-arrow-width)}.bs-popover-end>.popover-arrow:before,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow:before,.bs-popover-end>.popover-arrow:after,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow:after{border-width:calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * .5) 0}.bs-popover-end>.popover-arrow:before,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow:before{border-right-color:var(--bs-popover-arrow-border);left:0}.bs-popover-end>.popover-arrow:after,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow:after{left:var(--bs-popover-border-width);border-right-color:var(--bs-popover-bg)}.bs-popover-bottom>.popover-arrow,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow{top:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width))}.bs-popover-bottom>.popover-arrow:before,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow:before,.bs-popover-bottom>.popover-arrow:after,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow:after{border-width:0 calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height)}.bs-popover-bottom>.popover-arrow:before,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow:before{border-bottom-color:var(--bs-popover-arrow-border);top:0}.bs-popover-bottom>.popover-arrow:after,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow:after{top:var(--bs-popover-border-width);border-bottom-color:var(--bs-popover-bg)}.bs-popover-bottom .popover-header:before,.bs-popover-auto[data-popper-placement^=bottom] .popover-header:before{width:var(--bs-popover-arrow-width);margin-left:calc(-.5 * var(--bs-popover-arrow-width));content:"";border-bottom:var(--bs-popover-border-width) solid var(--bs-popover-header-bg);display:block;position:absolute;top:0;left:50%}.bs-popover-start>.popover-arrow,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow{right:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));width:var(--bs-popover-arrow-height);height:var(--bs-popover-arrow-width)}.bs-popover-start>.popover-arrow:before,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow:before,.bs-popover-start>.popover-arrow:after,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow:after{border-width:calc(var(--bs-popover-arrow-width) * .5) 0 calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height)}.bs-popover-start>.popover-arrow:before,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow:before{border-left-color:var(--bs-popover-arrow-border);right:0}.bs-popover-start>.popover-arrow:after,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow:after{right:var(--bs-popover-border-width);border-left-color:var(--bs-popover-bg)}.popover-header{padding:var(--bs-popover-header-padding-y) var(--bs-popover-header-padding-x);font-size:var(--bs-popover-header-font-size);color:var(--bs-popover-header-color);background-color:var(--bs-popover-header-bg);border-bottom:var(--bs-popover-border-width) solid var(--bs-popover-border-color);border-top-left-radius:var(--bs-popover-inner-border-radius);border-top-right-radius:var(--bs-popover-inner-border-radius);margin-bottom:0}.popover-header:empty{display:none}.popover-body{padding:var(--bs-popover-body-padding-y) var(--bs-popover-body-padding-x);color:var(--bs-popover-body-color)}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{width:100%;position:relative;overflow:hidden}.carousel-inner:after{clear:both;content:"";display:block}.carousel-item{float:left;backface-visibility:hidden;width:100%;margin-right:-100%;transition:transform .6s ease-in-out;display:none;position:relative}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item.active,.carousel-item-next,.carousel-item-prev{display:block}.carousel-item-next:not(.carousel-item-start),.active.carousel-item-end{transform:translate(100%)}.carousel-item-prev:not(.carousel-item-end),.active.carousel-item-start{transform:translate(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item.active,.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end{z-index:1;opacity:1}.carousel-fade .active.carousel-item-start,.carousel-fade .active.carousel-item-end{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-start,.carousel-fade .active.carousel-item-end{transition:none}}.carousel-control-prev,.carousel-control-next{z-index:1;color:#fff;text-align:center;width:15%;filter:var(--bs-carousel-control-icon-filter);opacity:.5;background:0 0;border:0;justify-content:center;align-items:center;padding:0;transition:opacity .15s;display:flex;position:absolute;top:0;bottom:0}@media (prefers-reduced-motion:reduce){.carousel-control-prev,.carousel-control-next{transition:none}}.carousel-control-prev:hover,.carousel-control-prev:focus,.carousel-control-next:hover,.carousel-control-next:focus{color:#fff;opacity:.9;outline:0;text-decoration:none}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-prev-icon,.carousel-control-next-icon{background-position:50%;background-repeat:no-repeat;background-size:100% 100%;width:2rem;height:2rem;display:inline-block}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708'/%3e%3c/svg%3e")}.carousel-indicators{z-index:2;justify-content:center;margin-bottom:1rem;margin-left:15%;margin-right:15%;padding:0;display:flex;position:absolute;bottom:0;left:0;right:0}.carousel-indicators [data-bs-target]{box-sizing:content-box;text-indent:-999px;cursor:pointer;background-color:var(--bs-carousel-indicator-active-bg);opacity:.5;background-clip:padding-box;border:10px solid #0000;border-left:0;border-right:0;flex:0 auto;width:30px;height:3px;margin-left:3px;margin-right:3px;padding:0;transition:opacity .6s}@media (prefers-reduced-motion:reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{color:var(--bs-carousel-caption-color);text-align:center;padding-top:1.25rem;padding-bottom:1.25rem;position:absolute;bottom:1.25rem;left:15%;right:15%}.carousel-dark{--bs-carousel-indicator-active-bg:#000;--bs-carousel-caption-color:#000;--bs-carousel-control-icon-filter:invert(1) grayscale(100)}:root,[data-bs-theme=light]{--bs-carousel-indicator-active-bg:#fff;--bs-carousel-caption-color:#fff;--bs-carousel-control-icon-filter: }[data-bs-theme=dark]{--bs-carousel-indicator-active-bg:#000;--bs-carousel-caption-color:#000;--bs-carousel-control-icon-filter:invert(1) grayscale(100)}.spinner-grow,.spinner-border{width:var(--bs-spinner-width);height:var(--bs-spinner-height);vertical-align:var(--bs-spinner-vertical-align);animation:var(--bs-spinner-animation-speed) linear infinite var(--bs-spinner-animation-name);border-radius:50%;flex-shrink:0;display:inline-block}@keyframes spinner-border{to{transform:rotate(360deg)}}.spinner-border{--bs-spinner-width:2rem;--bs-spinner-height:2rem;--bs-spinner-vertical-align:-.125em;--bs-spinner-border-width:.25em;--bs-spinner-animation-speed:.75s;--bs-spinner-animation-name:spinner-border;border:var(--bs-spinner-border-width) solid currentcolor;border-right-color:#0000}.spinner-border-sm{--bs-spinner-width:1rem;--bs-spinner-height:1rem;--bs-spinner-border-width:.2em}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{--bs-spinner-width:2rem;--bs-spinner-height:2rem;--bs-spinner-vertical-align:-.125em;--bs-spinner-animation-speed:.75s;--bs-spinner-animation-name:spinner-grow;opacity:0;background-color:currentColor}.spinner-grow-sm{--bs-spinner-width:1rem;--bs-spinner-height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{--bs-spinner-animation-speed:1.5s}}.offcanvas,.offcanvas-xxl,.offcanvas-xl,.offcanvas-lg,.offcanvas-md,.offcanvas-sm{--bs-offcanvas-zindex:1045;--bs-offcanvas-width:400px;--bs-offcanvas-height:30vh;--bs-offcanvas-padding-x:1rem;--bs-offcanvas-padding-y:1rem;--bs-offcanvas-color:var(--bs-body-color);--bs-offcanvas-bg:var(--bs-body-bg);--bs-offcanvas-border-width:var(--bs-border-width);--bs-offcanvas-border-color:var(--bs-border-color-translucent);--bs-offcanvas-box-shadow:var(--bs-box-shadow-sm);--bs-offcanvas-transition:transform .3s ease-in-out;--bs-offcanvas-title-line-height:1.5}@media (width<=575.98px){.offcanvas-sm{z-index:var(--bs-offcanvas-zindex);max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);transition:var(--bs-offcanvas-transition);background-clip:padding-box;outline:0;flex-direction:column;display:flex;position:fixed;bottom:0}}@media (width<=575.98px) and (prefers-reduced-motion:reduce){.offcanvas-sm{transition:none}}@media (width<=575.98px){.offcanvas-sm.offcanvas-start{width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;left:0;transform:translate(-100%)}.offcanvas-sm.offcanvas-end{width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;right:0;transform:translate(100%)}.offcanvas-sm.offcanvas-top{height:var(--bs-offcanvas-height);border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;top:0;left:0;right:0;transform:translateY(-100%)}.offcanvas-sm.offcanvas-bottom{height:var(--bs-offcanvas-height);border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;left:0;right:0;transform:translateY(100%)}.offcanvas-sm.showing,.offcanvas-sm.show:not(.hiding){transform:none}.offcanvas-sm.showing,.offcanvas-sm.hiding,.offcanvas-sm.show{visibility:visible}}@media (width>=576px){.offcanvas-sm{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:#0000!important}.offcanvas-sm .offcanvas-header{display:none}.offcanvas-sm .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible;background-color:#0000!important}}@media (width<=767.98px){.offcanvas-md{z-index:var(--bs-offcanvas-zindex);max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);transition:var(--bs-offcanvas-transition);background-clip:padding-box;outline:0;flex-direction:column;display:flex;position:fixed;bottom:0}}@media (width<=767.98px) and (prefers-reduced-motion:reduce){.offcanvas-md{transition:none}}@media (width<=767.98px){.offcanvas-md.offcanvas-start{width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;left:0;transform:translate(-100%)}.offcanvas-md.offcanvas-end{width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;right:0;transform:translate(100%)}.offcanvas-md.offcanvas-top{height:var(--bs-offcanvas-height);border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;top:0;left:0;right:0;transform:translateY(-100%)}.offcanvas-md.offcanvas-bottom{height:var(--bs-offcanvas-height);border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;left:0;right:0;transform:translateY(100%)}.offcanvas-md.showing,.offcanvas-md.show:not(.hiding){transform:none}.offcanvas-md.showing,.offcanvas-md.hiding,.offcanvas-md.show{visibility:visible}}@media (width>=768px){.offcanvas-md{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:#0000!important}.offcanvas-md .offcanvas-header{display:none}.offcanvas-md .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible;background-color:#0000!important}}@media (width<=991.98px){.offcanvas-lg{z-index:var(--bs-offcanvas-zindex);max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);transition:var(--bs-offcanvas-transition);background-clip:padding-box;outline:0;flex-direction:column;display:flex;position:fixed;bottom:0}}@media (width<=991.98px) and (prefers-reduced-motion:reduce){.offcanvas-lg{transition:none}}@media (width<=991.98px){.offcanvas-lg.offcanvas-start{width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;left:0;transform:translate(-100%)}.offcanvas-lg.offcanvas-end{width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;right:0;transform:translate(100%)}.offcanvas-lg.offcanvas-top{height:var(--bs-offcanvas-height);border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;top:0;left:0;right:0;transform:translateY(-100%)}.offcanvas-lg.offcanvas-bottom{height:var(--bs-offcanvas-height);border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;left:0;right:0;transform:translateY(100%)}.offcanvas-lg.showing,.offcanvas-lg.show:not(.hiding){transform:none}.offcanvas-lg.showing,.offcanvas-lg.hiding,.offcanvas-lg.show{visibility:visible}}@media (width>=992px){.offcanvas-lg{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:#0000!important}.offcanvas-lg .offcanvas-header{display:none}.offcanvas-lg .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible;background-color:#0000!important}}@media (width<=1199.98px){.offcanvas-xl{z-index:var(--bs-offcanvas-zindex);max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);transition:var(--bs-offcanvas-transition);background-clip:padding-box;outline:0;flex-direction:column;display:flex;position:fixed;bottom:0}}@media (width<=1199.98px) and (prefers-reduced-motion:reduce){.offcanvas-xl{transition:none}}@media (width<=1199.98px){.offcanvas-xl.offcanvas-start{width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;left:0;transform:translate(-100%)}.offcanvas-xl.offcanvas-end{width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;right:0;transform:translate(100%)}.offcanvas-xl.offcanvas-top{height:var(--bs-offcanvas-height);border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;top:0;left:0;right:0;transform:translateY(-100%)}.offcanvas-xl.offcanvas-bottom{height:var(--bs-offcanvas-height);border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;left:0;right:0;transform:translateY(100%)}.offcanvas-xl.showing,.offcanvas-xl.show:not(.hiding){transform:none}.offcanvas-xl.showing,.offcanvas-xl.hiding,.offcanvas-xl.show{visibility:visible}}@media (width>=1200px){.offcanvas-xl{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:#0000!important}.offcanvas-xl .offcanvas-header{display:none}.offcanvas-xl .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible;background-color:#0000!important}}@media (width<=1399.98px){.offcanvas-xxl{z-index:var(--bs-offcanvas-zindex);max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);transition:var(--bs-offcanvas-transition);background-clip:padding-box;outline:0;flex-direction:column;display:flex;position:fixed;bottom:0}}@media (width<=1399.98px) and (prefers-reduced-motion:reduce){.offcanvas-xxl{transition:none}}@media (width<=1399.98px){.offcanvas-xxl.offcanvas-start{width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;left:0;transform:translate(-100%)}.offcanvas-xxl.offcanvas-end{width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;right:0;transform:translate(100%)}.offcanvas-xxl.offcanvas-top{height:var(--bs-offcanvas-height);border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;top:0;left:0;right:0;transform:translateY(-100%)}.offcanvas-xxl.offcanvas-bottom{height:var(--bs-offcanvas-height);border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;left:0;right:0;transform:translateY(100%)}.offcanvas-xxl.showing,.offcanvas-xxl.show:not(.hiding){transform:none}.offcanvas-xxl.showing,.offcanvas-xxl.hiding,.offcanvas-xxl.show{visibility:visible}}@media (width>=1400px){.offcanvas-xxl{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:#0000!important}.offcanvas-xxl .offcanvas-header{display:none}.offcanvas-xxl .offcanvas-body{flex-grow:0;padding:0;display:flex;overflow-y:visible;background-color:#0000!important}}.offcanvas{z-index:var(--bs-offcanvas-zindex);max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);transition:var(--bs-offcanvas-transition);background-clip:padding-box;outline:0;flex-direction:column;display:flex;position:fixed;bottom:0}@media (prefers-reduced-motion:reduce){.offcanvas{transition:none}}.offcanvas.offcanvas-start{width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;left:0;transform:translate(-100%)}.offcanvas.offcanvas-end{width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);top:0;right:0;transform:translate(100%)}.offcanvas.offcanvas-top{height:var(--bs-offcanvas-height);border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;top:0;left:0;right:0;transform:translateY(-100%)}.offcanvas.offcanvas-bottom{height:var(--bs-offcanvas-height);border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);max-height:100%;left:0;right:0;transform:translateY(100%)}.offcanvas.showing,.offcanvas.show:not(.hiding){transform:none}.offcanvas.showing,.offcanvas.hiding,.offcanvas.show{visibility:visible}.offcanvas-backdrop{z-index:1040;background-color:#000;width:100vw;height:100vh;position:fixed;top:0;left:0}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{padding:var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x);align-items:center;display:flex}.offcanvas-header .btn-close{padding:calc(var(--bs-offcanvas-padding-y) * .5) calc(var(--bs-offcanvas-padding-x) * .5);margin-top:calc(-.5 * var(--bs-offcanvas-padding-y));margin-right:calc(-.5 * var(--bs-offcanvas-padding-x));margin-bottom:calc(-.5 * var(--bs-offcanvas-padding-y));margin-left:auto}.offcanvas-title{line-height:var(--bs-offcanvas-title-line-height);margin-bottom:0}.offcanvas-body{padding:var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x);flex-grow:1;overflow-y:auto}.placeholder{vertical-align:middle;cursor:wait;opacity:.5;background-color:currentColor;min-height:1em;display:inline-block}.placeholder.btn:before{content:"";display:inline-block}.placeholder-xs{min-height:.6em}.placeholder-sm{min-height:.8em}.placeholder-lg{min-height:1.2em}.placeholder-glow .placeholder{animation:2s ease-in-out infinite placeholder-glow}@keyframes placeholder-glow{50%{opacity:.2}}.placeholder-wave{animation:2s linear infinite placeholder-wave;-webkit-mask-image:linear-gradient(130deg,#000 55%,#000c 75%,#000 95%);mask-image:linear-gradient(130deg,#000 55%,#000c 75%,#000 95%);-webkit-mask-size:200% 100%;mask-size:200% 100%}@keyframes placeholder-wave{to{-webkit-mask-position:-200% 0;mask-position:-200% 0}}.clearfix:after{clear:both;content:"";display:block}.text-bg-primary{color:#fff!important;background-color:RGBA(var(--bs-primary-rgb), var(--bs-bg-opacity,1))!important}.text-bg-secondary{color:#fff!important;background-color:RGBA(var(--bs-secondary-rgb), var(--bs-bg-opacity,1))!important}.text-bg-success{color:#fff!important;background-color:RGBA(var(--bs-success-rgb), var(--bs-bg-opacity,1))!important}.text-bg-info{color:#000!important;background-color:RGBA(var(--bs-info-rgb), var(--bs-bg-opacity,1))!important}.text-bg-warning{color:#000!important;background-color:RGBA(var(--bs-warning-rgb), var(--bs-bg-opacity,1))!important}.text-bg-danger{color:#fff!important;background-color:RGBA(var(--bs-danger-rgb), var(--bs-bg-opacity,1))!important}.text-bg-light{color:#000!important;background-color:RGBA(var(--bs-light-rgb), var(--bs-bg-opacity,1))!important}.text-bg-dark{color:#fff!important;background-color:RGBA(var(--bs-dark-rgb), var(--bs-bg-opacity,1))!important}.link-primary{color:RGBA(var(--bs-primary-rgb), var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-primary-rgb), var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-primary-rgb), var(--bs-link-underline-opacity,1))!important}.link-primary:hover,.link-primary:focus{color:RGBA(10, 88, 202, var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(10, 88, 202, var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(10, 88, 202, var(--bs-link-underline-opacity,1))!important}.link-secondary{color:RGBA(var(--bs-secondary-rgb), var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-secondary-rgb), var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-secondary-rgb), var(--bs-link-underline-opacity,1))!important}.link-secondary:hover,.link-secondary:focus{color:RGBA(86, 94, 100, var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(86, 94, 100, var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(86, 94, 100, var(--bs-link-underline-opacity,1))!important}.link-success{color:RGBA(var(--bs-success-rgb), var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-success-rgb), var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-success-rgb), var(--bs-link-underline-opacity,1))!important}.link-success:hover,.link-success:focus{color:RGBA(20, 108, 67, var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(20, 108, 67, var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(20, 108, 67, var(--bs-link-underline-opacity,1))!important}.link-info{color:RGBA(var(--bs-info-rgb), var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-info-rgb), var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-info-rgb), var(--bs-link-underline-opacity,1))!important}.link-info:hover,.link-info:focus{color:RGBA(61, 213, 243, var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(61, 213, 243, var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(61, 213, 243, var(--bs-link-underline-opacity,1))!important}.link-warning{color:RGBA(var(--bs-warning-rgb), var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-warning-rgb), var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-warning-rgb), var(--bs-link-underline-opacity,1))!important}.link-warning:hover,.link-warning:focus{color:RGBA(255, 205, 57, var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(255, 205, 57, var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(255, 205, 57, var(--bs-link-underline-opacity,1))!important}.link-danger{color:RGBA(var(--bs-danger-rgb), var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-danger-rgb), var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-danger-rgb), var(--bs-link-underline-opacity,1))!important}.link-danger:hover,.link-danger:focus{color:RGBA(176, 42, 55, var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(176, 42, 55, var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(176, 42, 55, var(--bs-link-underline-opacity,1))!important}.link-light{color:RGBA(var(--bs-light-rgb), var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-light-rgb), var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-light-rgb), var(--bs-link-underline-opacity,1))!important}.link-light:hover,.link-light:focus{color:RGBA(249, 250, 251, var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(249, 250, 251, var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(249, 250, 251, var(--bs-link-underline-opacity,1))!important}.link-dark{color:RGBA(var(--bs-dark-rgb), var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-dark-rgb), var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-dark-rgb), var(--bs-link-underline-opacity,1))!important}.link-dark:hover,.link-dark:focus{color:RGBA(26, 30, 33, var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(26, 30, 33, var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(26, 30, 33, var(--bs-link-underline-opacity,1))!important}.link-body-emphasis{color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity,1))!important}.link-body-emphasis:hover,.link-body-emphasis:focus{color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-opacity,.75))!important;-webkit-text-decoration-color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity,.75))!important;text-decoration-color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity,.75))!important}.focus-ring:focus{box-shadow:var(--bs-focus-ring-x,0) var(--bs-focus-ring-y,0) var(--bs-focus-ring-blur,0) var(--bs-focus-ring-width) var(--bs-focus-ring-color);outline:0}.icon-link{-webkit-text-decoration-color:rgba(var(--bs-link-color-rgb), var(--bs-link-opacity,.5));text-decoration-color:rgba(var(--bs-link-color-rgb), var(--bs-link-opacity,.5));text-underline-offset:.25em;backface-visibility:hidden;align-items:center;gap:.375rem;display:inline-flex}.icon-link>.bi{fill:currentColor;flex-shrink:0;width:1em;height:1em;transition:transform .2s ease-in-out}@media (prefers-reduced-motion:reduce){.icon-link>.bi{transition:none}}.icon-link-hover:hover>.bi,.icon-link-hover:focus-visible>.bi{transform:var(--bs-icon-link-transform,translate3d(.25em, 0, 0))}.ratio{width:100%;position:relative}.ratio:before{padding-top:var(--bs-aspect-ratio);content:"";display:block}.ratio>*{width:100%;height:100%;position:absolute;top:0;left:0}.ratio-1x1{--bs-aspect-ratio:100%}.ratio-4x3{--bs-aspect-ratio:75%}.ratio-16x9{--bs-aspect-ratio:56.25%}.ratio-21x9{--bs-aspect-ratio:42.8571%}.fixed-top{z-index:1030;position:fixed;top:0;left:0;right:0}.fixed-bottom{z-index:1030;position:fixed;bottom:0;left:0;right:0}.sticky-top{z-index:1020;position:sticky;top:0}.sticky-bottom{z-index:1020;position:sticky;bottom:0}@media (width>=576px){.sticky-sm-top{z-index:1020;position:sticky;top:0}.sticky-sm-bottom{z-index:1020;position:sticky;bottom:0}}@media (width>=768px){.sticky-md-top{z-index:1020;position:sticky;top:0}.sticky-md-bottom{z-index:1020;position:sticky;bottom:0}}@media (width>=992px){.sticky-lg-top{z-index:1020;position:sticky;top:0}.sticky-lg-bottom{z-index:1020;position:sticky;bottom:0}}@media (width>=1200px){.sticky-xl-top{z-index:1020;position:sticky;top:0}.sticky-xl-bottom{z-index:1020;position:sticky;bottom:0}}@media (width>=1400px){.sticky-xxl-top{z-index:1020;position:sticky;top:0}.sticky-xxl-bottom{z-index:1020;position:sticky;bottom:0}}.hstack{flex-direction:row;align-self:stretch;align-items:center;display:flex}.vstack{flex-direction:column;flex:auto;align-self:stretch;display:flex}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){clip:rect(0, 0, 0, 0)!important;white-space:nowrap!important;border:0!important;width:1px!important;height:1px!important;margin:-1px!important;padding:0!important;overflow:hidden!important}.visually-hidden:not(caption),.visually-hidden-focusable:not(:focus):not(:focus-within):not(caption){position:absolute!important}.visually-hidden *,.visually-hidden-focusable:not(:focus):not(:focus-within) *{overflow:hidden!important}.stretched-link:after{z-index:1;content:"";position:absolute;inset:0}.text-truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.vr{width:var(--bs-border-width);opacity:.25;background-color:currentColor;align-self:stretch;min-height:1em;display:inline-block}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.float-start{float:left!important}.float-end{float:right!important}.float-none{float:none!important}.object-fit-contain{object-fit:contain!important}.object-fit-cover{object-fit:cover!important}.object-fit-fill{object-fit:fill!important}.object-fit-scale{object-fit:scale-down!important}.object-fit-none{object-fit:none!important}.opacity-0{opacity:0!important}.opacity-25{opacity:.25!important}.opacity-50{opacity:.5!important}.opacity-75{opacity:.75!important}.opacity-100{opacity:1!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-visible{overflow:visible!important}.overflow-scroll{overflow:scroll!important}.overflow-x-auto{overflow-x:auto!important}.overflow-x-hidden{overflow-x:hidden!important}.overflow-x-visible{overflow-x:visible!important}.overflow-x-scroll{overflow-x:scroll!important}.overflow-y-auto{overflow-y:auto!important}.overflow-y-hidden{overflow-y:hidden!important}.overflow-y-visible{overflow-y:visible!important}.overflow-y-scroll{overflow-y:scroll!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-grid{display:grid!important}.d-inline-grid{display:inline-grid!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}.d-none{display:none!important}.shadow{box-shadow:var(--bs-box-shadow)!important}.shadow-sm{box-shadow:var(--bs-box-shadow-sm)!important}.shadow-lg{box-shadow:var(--bs-box-shadow-lg)!important}.shadow-none{box-shadow:none!important}.focus-ring-primary{--bs-focus-ring-color:rgba(var(--bs-primary-rgb), var(--bs-focus-ring-opacity))}.focus-ring-secondary{--bs-focus-ring-color:rgba(var(--bs-secondary-rgb), var(--bs-focus-ring-opacity))}.focus-ring-success{--bs-focus-ring-color:rgba(var(--bs-success-rgb), var(--bs-focus-ring-opacity))}.focus-ring-info{--bs-focus-ring-color:rgba(var(--bs-info-rgb), var(--bs-focus-ring-opacity))}.focus-ring-warning{--bs-focus-ring-color:rgba(var(--bs-warning-rgb), var(--bs-focus-ring-opacity))}.focus-ring-danger{--bs-focus-ring-color:rgba(var(--bs-danger-rgb), var(--bs-focus-ring-opacity))}.focus-ring-light{--bs-focus-ring-color:rgba(var(--bs-light-rgb), var(--bs-focus-ring-opacity))}.focus-ring-dark{--bs-focus-ring-color:rgba(var(--bs-dark-rgb), var(--bs-focus-ring-opacity))}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:sticky!important}.top-0{top:0!important}.top-50{top:50%!important}.top-100{top:100%!important}.bottom-0{bottom:0!important}.bottom-50{bottom:50%!important}.bottom-100{bottom:100%!important}.start-0{left:0!important}.start-50{left:50%!important}.start-100{left:100%!important}.end-0{right:0!important}.end-50{right:50%!important}.end-100{right:100%!important}.translate-middle{transform:translate(-50%,-50%)!important}.translate-middle-x{transform:translate(-50%)!important}.translate-middle-y{transform:translateY(-50%)!important}.border{border:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-0{border:0!important}.border-top{border-top:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-top-0{border-top:0!important}.border-end{border-right:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-end-0{border-right:0!important}.border-bottom{border-bottom:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-bottom-0{border-bottom:0!important}.border-start{border-left:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-start-0{border-left:0!important}.border-primary{--bs-border-opacity:1;border-color:rgba(var(--bs-primary-rgb), var(--bs-border-opacity))!important}.border-secondary{--bs-border-opacity:1;border-color:rgba(var(--bs-secondary-rgb), var(--bs-border-opacity))!important}.border-success{--bs-border-opacity:1;border-color:rgba(var(--bs-success-rgb), var(--bs-border-opacity))!important}.border-info{--bs-border-opacity:1;border-color:rgba(var(--bs-info-rgb), var(--bs-border-opacity))!important}.border-warning{--bs-border-opacity:1;border-color:rgba(var(--bs-warning-rgb), var(--bs-border-opacity))!important}.border-danger{--bs-border-opacity:1;border-color:rgba(var(--bs-danger-rgb), var(--bs-border-opacity))!important}.border-light{--bs-border-opacity:1;border-color:rgba(var(--bs-light-rgb), var(--bs-border-opacity))!important}.border-dark{--bs-border-opacity:1;border-color:rgba(var(--bs-dark-rgb), var(--bs-border-opacity))!important}.border-black{--bs-border-opacity:1;border-color:rgba(var(--bs-black-rgb), var(--bs-border-opacity))!important}.border-white{--bs-border-opacity:1;border-color:rgba(var(--bs-white-rgb), var(--bs-border-opacity))!important}.border-primary-subtle{border-color:var(--bs-primary-border-subtle)!important}.border-secondary-subtle{border-color:var(--bs-secondary-border-subtle)!important}.border-success-subtle{border-color:var(--bs-success-border-subtle)!important}.border-info-subtle{border-color:var(--bs-info-border-subtle)!important}.border-warning-subtle{border-color:var(--bs-warning-border-subtle)!important}.border-danger-subtle{border-color:var(--bs-danger-border-subtle)!important}.border-light-subtle{border-color:var(--bs-light-border-subtle)!important}.border-dark-subtle{border-color:var(--bs-dark-border-subtle)!important}.border-1{border-width:1px!important}.border-2{border-width:2px!important}.border-3{border-width:3px!important}.border-4{border-width:4px!important}.border-5{border-width:5px!important}.border-opacity-10{--bs-border-opacity:.1}.border-opacity-25{--bs-border-opacity:.25}.border-opacity-50{--bs-border-opacity:.5}.border-opacity-75{--bs-border-opacity:.75}.border-opacity-100{--bs-border-opacity:1}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.mw-100{max-width:100%!important}.vw-100{width:100vw!important}.min-vw-100{min-width:100vw!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mh-100{max-height:100%!important}.vh-100{height:100vh!important}.min-vh-100{min-height:100vh!important}.flex-fill{flex:auto!important}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.justify-content-evenly{justify-content:space-evenly!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}.order-first{order:-1!important}.order-0{order:0!important}.order-1{order:1!important}.order-2{order:2!important}.order-3{order:3!important}.order-4{order:4!important}.order-5{order:5!important}.order-last{order:6!important}.m-0{margin:0!important}.m-1{margin:.25rem!important}.m-2{margin:.5rem!important}.m-3{margin:1rem!important}.m-4{margin:1.5rem!important}.m-5{margin:3rem!important}.m-auto{margin:auto!important}.mx-0{margin-left:0!important;margin-right:0!important}.mx-1{margin-left:.25rem!important;margin-right:.25rem!important}.mx-2{margin-left:.5rem!important;margin-right:.5rem!important}.mx-3{margin-left:1rem!important;margin-right:1rem!important}.mx-4{margin-left:1.5rem!important;margin-right:1.5rem!important}.mx-5{margin-left:3rem!important;margin-right:3rem!important}.mx-auto{margin-left:auto!important;margin-right:auto!important}.my-0{margin-top:0!important;margin-bottom:0!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-0{margin-top:0!important}.mt-1{margin-top:.25rem!important}.mt-2{margin-top:.5rem!important}.mt-3{margin-top:1rem!important}.mt-4{margin-top:1.5rem!important}.mt-5{margin-top:3rem!important}.mt-auto{margin-top:auto!important}.me-0{margin-right:0!important}.me-1{margin-right:.25rem!important}.me-2{margin-right:.5rem!important}.me-3{margin-right:1rem!important}.me-4{margin-right:1.5rem!important}.me-5{margin-right:3rem!important}.me-auto{margin-right:auto!important}.mb-0{margin-bottom:0!important}.mb-1{margin-bottom:.25rem!important}.mb-2{margin-bottom:.5rem!important}.mb-3{margin-bottom:1rem!important}.mb-4{margin-bottom:1.5rem!important}.mb-5{margin-bottom:3rem!important}.mb-auto{margin-bottom:auto!important}.ms-0{margin-left:0!important}.ms-1{margin-left:.25rem!important}.ms-2{margin-left:.5rem!important}.ms-3{margin-left:1rem!important}.ms-4{margin-left:1.5rem!important}.ms-5{margin-left:3rem!important}.ms-auto{margin-left:auto!important}.p-0{padding:0!important}.p-1{padding:.25rem!important}.p-2{padding:.5rem!important}.p-3{padding:1rem!important}.p-4{padding:1.5rem!important}.p-5{padding:3rem!important}.px-0{padding-left:0!important;padding-right:0!important}.px-1{padding-left:.25rem!important;padding-right:.25rem!important}.px-2{padding-left:.5rem!important;padding-right:.5rem!important}.px-3{padding-left:1rem!important;padding-right:1rem!important}.px-4{padding-left:1.5rem!important;padding-right:1.5rem!important}.px-5{padding-left:3rem!important;padding-right:3rem!important}.py-0{padding-top:0!important;padding-bottom:0!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-0{padding-top:0!important}.pt-1{padding-top:.25rem!important}.pt-2{padding-top:.5rem!important}.pt-3{padding-top:1rem!important}.pt-4{padding-top:1.5rem!important}.pt-5{padding-top:3rem!important}.pe-0{padding-right:0!important}.pe-1{padding-right:.25rem!important}.pe-2{padding-right:.5rem!important}.pe-3{padding-right:1rem!important}.pe-4{padding-right:1.5rem!important}.pe-5{padding-right:3rem!important}.pb-0{padding-bottom:0!important}.pb-1{padding-bottom:.25rem!important}.pb-2{padding-bottom:.5rem!important}.pb-3{padding-bottom:1rem!important}.pb-4{padding-bottom:1.5rem!important}.pb-5{padding-bottom:3rem!important}.ps-0{padding-left:0!important}.ps-1{padding-left:.25rem!important}.ps-2{padding-left:.5rem!important}.ps-3{padding-left:1rem!important}.ps-4{padding-left:1.5rem!important}.ps-5{padding-left:3rem!important}.gap-0{gap:0!important}.gap-1{gap:.25rem!important}.gap-2{gap:.5rem!important}.gap-3{gap:1rem!important}.gap-4{gap:1.5rem!important}.gap-5{gap:3rem!important}.row-gap-0{row-gap:0!important}.row-gap-1{row-gap:.25rem!important}.row-gap-2{row-gap:.5rem!important}.row-gap-3{row-gap:1rem!important}.row-gap-4{row-gap:1.5rem!important}.row-gap-5{row-gap:3rem!important}.column-gap-0{column-gap:0!important}.column-gap-1{column-gap:.25rem!important}.column-gap-2{column-gap:.5rem!important}.column-gap-3{column-gap:1rem!important}.column-gap-4{column-gap:1.5rem!important}.column-gap-5{column-gap:3rem!important}.font-monospace{font-family:var(--bs-font-monospace)!important}.fs-1{font-size:calc(1.375rem + 1.5vw)!important}.fs-2{font-size:calc(1.325rem + .9vw)!important}.fs-3{font-size:calc(1.3rem + .6vw)!important}.fs-4{font-size:calc(1.275rem + .3vw)!important}.fs-5{font-size:1.25rem!important}.fs-6{font-size:1rem!important}.fst-italic{font-style:italic!important}.fst-normal{font-style:normal!important}.fw-lighter{font-weight:lighter!important}.fw-light{font-weight:300!important}.fw-normal{font-weight:400!important}.fw-medium{font-weight:500!important}.fw-semibold{font-weight:600!important}.fw-bold{font-weight:700!important}.fw-bolder{font-weight:bolder!important}.lh-1{line-height:1!important}.lh-sm{line-height:1.25!important}.lh-base{line-height:1.5!important}.lh-lg{line-height:2!important}.text-start{text-align:left!important}.text-end{text-align:right!important}.text-center{text-align:center!important}.text-decoration-none{text-decoration:none!important}.text-decoration-underline{text-decoration:underline!important}.text-decoration-line-through{text-decoration:line-through!important}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-break{word-wrap:break-word!important;word-break:break-word!important}.text-primary{--bs-text-opacity:1;color:rgba(var(--bs-primary-rgb), var(--bs-text-opacity))!important}.text-secondary{--bs-text-opacity:1;color:rgba(var(--bs-secondary-rgb), var(--bs-text-opacity))!important}.text-success{--bs-text-opacity:1;color:rgba(var(--bs-success-rgb), var(--bs-text-opacity))!important}.text-info{--bs-text-opacity:1;color:rgba(var(--bs-info-rgb), var(--bs-text-opacity))!important}.text-warning{--bs-text-opacity:1;color:rgba(var(--bs-warning-rgb), var(--bs-text-opacity))!important}.text-danger{--bs-text-opacity:1;color:rgba(var(--bs-danger-rgb), var(--bs-text-opacity))!important}.text-light{--bs-text-opacity:1;color:rgba(var(--bs-light-rgb), var(--bs-text-opacity))!important}.text-dark{--bs-text-opacity:1;color:rgba(var(--bs-dark-rgb), var(--bs-text-opacity))!important}.text-black{--bs-text-opacity:1;color:rgba(var(--bs-black-rgb), var(--bs-text-opacity))!important}.text-white{--bs-text-opacity:1;color:rgba(var(--bs-white-rgb), var(--bs-text-opacity))!important}.text-body{--bs-text-opacity:1;color:rgba(var(--bs-body-color-rgb), var(--bs-text-opacity))!important}.text-muted{--bs-text-opacity:1;color:var(--bs-secondary-color)!important}.text-black-50{--bs-text-opacity:1;color:#00000080!important}.text-white-50{--bs-text-opacity:1;color:#ffffff80!important}.text-body-secondary{--bs-text-opacity:1;color:var(--bs-secondary-color)!important}.text-body-tertiary{--bs-text-opacity:1;color:var(--bs-tertiary-color)!important}.text-body-emphasis{--bs-text-opacity:1;color:var(--bs-emphasis-color)!important}.text-reset{--bs-text-opacity:1;color:inherit!important}.text-opacity-25{--bs-text-opacity:.25}.text-opacity-50{--bs-text-opacity:.5}.text-opacity-75{--bs-text-opacity:.75}.text-opacity-100{--bs-text-opacity:1}.text-primary-emphasis{color:var(--bs-primary-text-emphasis)!important}.text-secondary-emphasis{color:var(--bs-secondary-text-emphasis)!important}.text-success-emphasis{color:var(--bs-success-text-emphasis)!important}.text-info-emphasis{color:var(--bs-info-text-emphasis)!important}.text-warning-emphasis{color:var(--bs-warning-text-emphasis)!important}.text-danger-emphasis{color:var(--bs-danger-text-emphasis)!important}.text-light-emphasis{color:var(--bs-light-text-emphasis)!important}.text-dark-emphasis{color:var(--bs-dark-text-emphasis)!important}.link-opacity-10,.link-opacity-10-hover:hover{--bs-link-opacity:.1}.link-opacity-25,.link-opacity-25-hover:hover{--bs-link-opacity:.25}.link-opacity-50,.link-opacity-50-hover:hover{--bs-link-opacity:.5}.link-opacity-75,.link-opacity-75-hover:hover{--bs-link-opacity:.75}.link-opacity-100,.link-opacity-100-hover:hover{--bs-link-opacity:1}.link-offset-1,.link-offset-1-hover:hover{text-underline-offset:.125em!important}.link-offset-2,.link-offset-2-hover:hover{text-underline-offset:.25em!important}.link-offset-3,.link-offset-3-hover:hover{text-underline-offset:.375em!important}.link-underline-primary{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-primary-rgb), var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-primary-rgb), var(--bs-link-underline-opacity))!important}.link-underline-secondary{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-secondary-rgb), var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-secondary-rgb), var(--bs-link-underline-opacity))!important}.link-underline-success{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-success-rgb), var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-success-rgb), var(--bs-link-underline-opacity))!important}.link-underline-info{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-info-rgb), var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-info-rgb), var(--bs-link-underline-opacity))!important}.link-underline-warning{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-warning-rgb), var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-warning-rgb), var(--bs-link-underline-opacity))!important}.link-underline-danger{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-danger-rgb), var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-danger-rgb), var(--bs-link-underline-opacity))!important}.link-underline-light{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-light-rgb), var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-light-rgb), var(--bs-link-underline-opacity))!important}.link-underline-dark{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-dark-rgb), var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-dark-rgb), var(--bs-link-underline-opacity))!important}.link-underline{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-link-color-rgb), var(--bs-link-underline-opacity,1))!important;text-decoration-color:rgba(var(--bs-link-color-rgb), var(--bs-link-underline-opacity,1))!important}.link-underline-opacity-0,.link-underline-opacity-0-hover:hover{--bs-link-underline-opacity:0}.link-underline-opacity-10,.link-underline-opacity-10-hover:hover{--bs-link-underline-opacity:.1}.link-underline-opacity-25,.link-underline-opacity-25-hover:hover{--bs-link-underline-opacity:.25}.link-underline-opacity-50,.link-underline-opacity-50-hover:hover{--bs-link-underline-opacity:.5}.link-underline-opacity-75,.link-underline-opacity-75-hover:hover{--bs-link-underline-opacity:.75}.link-underline-opacity-100,.link-underline-opacity-100-hover:hover{--bs-link-underline-opacity:1}.bg-primary{--bs-bg-opacity:1;background-color:rgba(var(--bs-primary-rgb), var(--bs-bg-opacity))!important}.bg-secondary{--bs-bg-opacity:1;background-color:rgba(var(--bs-secondary-rgb), var(--bs-bg-opacity))!important}.bg-success{--bs-bg-opacity:1;background-color:rgba(var(--bs-success-rgb), var(--bs-bg-opacity))!important}.bg-info{--bs-bg-opacity:1;background-color:rgba(var(--bs-info-rgb), var(--bs-bg-opacity))!important}.bg-warning{--bs-bg-opacity:1;background-color:rgba(var(--bs-warning-rgb), var(--bs-bg-opacity))!important}.bg-danger{--bs-bg-opacity:1;background-color:rgba(var(--bs-danger-rgb), var(--bs-bg-opacity))!important}.bg-light{--bs-bg-opacity:1;background-color:rgba(var(--bs-light-rgb), var(--bs-bg-opacity))!important}.bg-dark{--bs-bg-opacity:1;background-color:rgba(var(--bs-dark-rgb), var(--bs-bg-opacity))!important}.bg-black{--bs-bg-opacity:1;background-color:rgba(var(--bs-black-rgb), var(--bs-bg-opacity))!important}.bg-white{--bs-bg-opacity:1;background-color:rgba(var(--bs-white-rgb), var(--bs-bg-opacity))!important}.bg-body{--bs-bg-opacity:1;background-color:rgba(var(--bs-body-bg-rgb), var(--bs-bg-opacity))!important}.bg-transparent{--bs-bg-opacity:1;background-color:#0000!important}.bg-body-secondary{--bs-bg-opacity:1;background-color:rgba(var(--bs-secondary-bg-rgb), var(--bs-bg-opacity))!important}.bg-body-tertiary{--bs-bg-opacity:1;background-color:rgba(var(--bs-tertiary-bg-rgb), var(--bs-bg-opacity))!important}.bg-opacity-10{--bs-bg-opacity:.1}.bg-opacity-25{--bs-bg-opacity:.25}.bg-opacity-50{--bs-bg-opacity:.5}.bg-opacity-75{--bs-bg-opacity:.75}.bg-opacity-100{--bs-bg-opacity:1}.bg-primary-subtle{background-color:var(--bs-primary-bg-subtle)!important}.bg-secondary-subtle{background-color:var(--bs-secondary-bg-subtle)!important}.bg-success-subtle{background-color:var(--bs-success-bg-subtle)!important}.bg-info-subtle{background-color:var(--bs-info-bg-subtle)!important}.bg-warning-subtle{background-color:var(--bs-warning-bg-subtle)!important}.bg-danger-subtle{background-color:var(--bs-danger-bg-subtle)!important}.bg-light-subtle{background-color:var(--bs-light-bg-subtle)!important}.bg-dark-subtle{background-color:var(--bs-dark-bg-subtle)!important}.bg-gradient{background-image:var(--bs-gradient)!important}.user-select-all{-webkit-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;user-select:none!important}.pe-none{pointer-events:none!important}.pe-auto{pointer-events:auto!important}.rounded{border-radius:var(--bs-border-radius)!important}.rounded-0{border-radius:0!important}.rounded-1{border-radius:var(--bs-border-radius-sm)!important}.rounded-2{border-radius:var(--bs-border-radius)!important}.rounded-3{border-radius:var(--bs-border-radius-lg)!important}.rounded-4{border-radius:var(--bs-border-radius-xl)!important}.rounded-5{border-radius:var(--bs-border-radius-xxl)!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:var(--bs-border-radius-pill)!important}.rounded-top{border-top-left-radius:var(--bs-border-radius)!important;border-top-right-radius:var(--bs-border-radius)!important}.rounded-top-0{border-top-left-radius:0!important;border-top-right-radius:0!important}.rounded-top-1{border-top-left-radius:var(--bs-border-radius-sm)!important;border-top-right-radius:var(--bs-border-radius-sm)!important}.rounded-top-2{border-top-left-radius:var(--bs-border-radius)!important;border-top-right-radius:var(--bs-border-radius)!important}.rounded-top-3{border-top-left-radius:var(--bs-border-radius-lg)!important;border-top-right-radius:var(--bs-border-radius-lg)!important}.rounded-top-4{border-top-left-radius:var(--bs-border-radius-xl)!important;border-top-right-radius:var(--bs-border-radius-xl)!important}.rounded-top-5{border-top-left-radius:var(--bs-border-radius-xxl)!important;border-top-right-radius:var(--bs-border-radius-xxl)!important}.rounded-top-circle{border-top-left-radius:50%!important;border-top-right-radius:50%!important}.rounded-top-pill{border-top-left-radius:var(--bs-border-radius-pill)!important;border-top-right-radius:var(--bs-border-radius-pill)!important}.rounded-end{border-top-right-radius:var(--bs-border-radius)!important;border-bottom-right-radius:var(--bs-border-radius)!important}.rounded-end-0{border-top-right-radius:0!important;border-bottom-right-radius:0!important}.rounded-end-1{border-top-right-radius:var(--bs-border-radius-sm)!important;border-bottom-right-radius:var(--bs-border-radius-sm)!important}.rounded-end-2{border-top-right-radius:var(--bs-border-radius)!important;border-bottom-right-radius:var(--bs-border-radius)!important}.rounded-end-3{border-top-right-radius:var(--bs-border-radius-lg)!important;border-bottom-right-radius:var(--bs-border-radius-lg)!important}.rounded-end-4{border-top-right-radius:var(--bs-border-radius-xl)!important;border-bottom-right-radius:var(--bs-border-radius-xl)!important}.rounded-end-5{border-top-right-radius:var(--bs-border-radius-xxl)!important;border-bottom-right-radius:var(--bs-border-radius-xxl)!important}.rounded-end-circle{border-top-right-radius:50%!important;border-bottom-right-radius:50%!important}.rounded-end-pill{border-top-right-radius:var(--bs-border-radius-pill)!important;border-bottom-right-radius:var(--bs-border-radius-pill)!important}.rounded-bottom{border-bottom-right-radius:var(--bs-border-radius)!important;border-bottom-left-radius:var(--bs-border-radius)!important}.rounded-bottom-0{border-bottom-right-radius:0!important;border-bottom-left-radius:0!important}.rounded-bottom-1{border-bottom-right-radius:var(--bs-border-radius-sm)!important;border-bottom-left-radius:var(--bs-border-radius-sm)!important}.rounded-bottom-2{border-bottom-right-radius:var(--bs-border-radius)!important;border-bottom-left-radius:var(--bs-border-radius)!important}.rounded-bottom-3{border-bottom-right-radius:var(--bs-border-radius-lg)!important;border-bottom-left-radius:var(--bs-border-radius-lg)!important}.rounded-bottom-4{border-bottom-right-radius:var(--bs-border-radius-xl)!important;border-bottom-left-radius:var(--bs-border-radius-xl)!important}.rounded-bottom-5{border-bottom-right-radius:var(--bs-border-radius-xxl)!important;border-bottom-left-radius:var(--bs-border-radius-xxl)!important}.rounded-bottom-circle{border-bottom-right-radius:50%!important;border-bottom-left-radius:50%!important}.rounded-bottom-pill{border-bottom-right-radius:var(--bs-border-radius-pill)!important;border-bottom-left-radius:var(--bs-border-radius-pill)!important}.rounded-start{border-bottom-left-radius:var(--bs-border-radius)!important;border-top-left-radius:var(--bs-border-radius)!important}.rounded-start-0{border-top-left-radius:0!important;border-bottom-left-radius:0!important}.rounded-start-1{border-bottom-left-radius:var(--bs-border-radius-sm)!important;border-top-left-radius:var(--bs-border-radius-sm)!important}.rounded-start-2{border-bottom-left-radius:var(--bs-border-radius)!important;border-top-left-radius:var(--bs-border-radius)!important}.rounded-start-3{border-bottom-left-radius:var(--bs-border-radius-lg)!important;border-top-left-radius:var(--bs-border-radius-lg)!important}.rounded-start-4{border-bottom-left-radius:var(--bs-border-radius-xl)!important;border-top-left-radius:var(--bs-border-radius-xl)!important}.rounded-start-5{border-bottom-left-radius:var(--bs-border-radius-xxl)!important;border-top-left-radius:var(--bs-border-radius-xxl)!important}.rounded-start-circle{border-top-left-radius:50%!important;border-bottom-left-radius:50%!important}.rounded-start-pill{border-bottom-left-radius:var(--bs-border-radius-pill)!important;border-top-left-radius:var(--bs-border-radius-pill)!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}.z-n1{z-index:-1!important}.z-0{z-index:0!important}.z-1{z-index:1!important}.z-2{z-index:2!important}.z-3{z-index:3!important}@media (width>=576px){.float-sm-start{float:left!important}.float-sm-end{float:right!important}.float-sm-none{float:none!important}.object-fit-sm-contain{object-fit:contain!important}.object-fit-sm-cover{object-fit:cover!important}.object-fit-sm-fill{object-fit:fill!important}.object-fit-sm-scale{object-fit:scale-down!important}.object-fit-sm-none{object-fit:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-grid{display:grid!important}.d-sm-inline-grid{display:inline-grid!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}.d-sm-none{display:none!important}.flex-sm-fill{flex:auto!important}.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.justify-content-sm-evenly{justify-content:space-evenly!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}.order-sm-first{order:-1!important}.order-sm-0{order:0!important}.order-sm-1{order:1!important}.order-sm-2{order:2!important}.order-sm-3{order:3!important}.order-sm-4{order:4!important}.order-sm-5{order:5!important}.order-sm-last{order:6!important}.m-sm-0{margin:0!important}.m-sm-1{margin:.25rem!important}.m-sm-2{margin:.5rem!important}.m-sm-3{margin:1rem!important}.m-sm-4{margin:1.5rem!important}.m-sm-5{margin:3rem!important}.m-sm-auto{margin:auto!important}.mx-sm-0{margin-left:0!important;margin-right:0!important}.mx-sm-1{margin-left:.25rem!important;margin-right:.25rem!important}.mx-sm-2{margin-left:.5rem!important;margin-right:.5rem!important}.mx-sm-3{margin-left:1rem!important;margin-right:1rem!important}.mx-sm-4{margin-left:1.5rem!important;margin-right:1.5rem!important}.mx-sm-5{margin-left:3rem!important;margin-right:3rem!important}.mx-sm-auto{margin-left:auto!important;margin-right:auto!important}.my-sm-0{margin-top:0!important;margin-bottom:0!important}.my-sm-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-sm-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-sm-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-sm-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-sm-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-sm-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-sm-0{margin-top:0!important}.mt-sm-1{margin-top:.25rem!important}.mt-sm-2{margin-top:.5rem!important}.mt-sm-3{margin-top:1rem!important}.mt-sm-4{margin-top:1.5rem!important}.mt-sm-5{margin-top:3rem!important}.mt-sm-auto{margin-top:auto!important}.me-sm-0{margin-right:0!important}.me-sm-1{margin-right:.25rem!important}.me-sm-2{margin-right:.5rem!important}.me-sm-3{margin-right:1rem!important}.me-sm-4{margin-right:1.5rem!important}.me-sm-5{margin-right:3rem!important}.me-sm-auto{margin-right:auto!important}.mb-sm-0{margin-bottom:0!important}.mb-sm-1{margin-bottom:.25rem!important}.mb-sm-2{margin-bottom:.5rem!important}.mb-sm-3{margin-bottom:1rem!important}.mb-sm-4{margin-bottom:1.5rem!important}.mb-sm-5{margin-bottom:3rem!important}.mb-sm-auto{margin-bottom:auto!important}.ms-sm-0{margin-left:0!important}.ms-sm-1{margin-left:.25rem!important}.ms-sm-2{margin-left:.5rem!important}.ms-sm-3{margin-left:1rem!important}.ms-sm-4{margin-left:1.5rem!important}.ms-sm-5{margin-left:3rem!important}.ms-sm-auto{margin-left:auto!important}.p-sm-0{padding:0!important}.p-sm-1{padding:.25rem!important}.p-sm-2{padding:.5rem!important}.p-sm-3{padding:1rem!important}.p-sm-4{padding:1.5rem!important}.p-sm-5{padding:3rem!important}.px-sm-0{padding-left:0!important;padding-right:0!important}.px-sm-1{padding-left:.25rem!important;padding-right:.25rem!important}.px-sm-2{padding-left:.5rem!important;padding-right:.5rem!important}.px-sm-3{padding-left:1rem!important;padding-right:1rem!important}.px-sm-4{padding-left:1.5rem!important;padding-right:1.5rem!important}.px-sm-5{padding-left:3rem!important;padding-right:3rem!important}.py-sm-0{padding-top:0!important;padding-bottom:0!important}.py-sm-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-sm-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-sm-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-sm-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-sm-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-sm-0{padding-top:0!important}.pt-sm-1{padding-top:.25rem!important}.pt-sm-2{padding-top:.5rem!important}.pt-sm-3{padding-top:1rem!important}.pt-sm-4{padding-top:1.5rem!important}.pt-sm-5{padding-top:3rem!important}.pe-sm-0{padding-right:0!important}.pe-sm-1{padding-right:.25rem!important}.pe-sm-2{padding-right:.5rem!important}.pe-sm-3{padding-right:1rem!important}.pe-sm-4{padding-right:1.5rem!important}.pe-sm-5{padding-right:3rem!important}.pb-sm-0{padding-bottom:0!important}.pb-sm-1{padding-bottom:.25rem!important}.pb-sm-2{padding-bottom:.5rem!important}.pb-sm-3{padding-bottom:1rem!important}.pb-sm-4{padding-bottom:1.5rem!important}.pb-sm-5{padding-bottom:3rem!important}.ps-sm-0{padding-left:0!important}.ps-sm-1{padding-left:.25rem!important}.ps-sm-2{padding-left:.5rem!important}.ps-sm-3{padding-left:1rem!important}.ps-sm-4{padding-left:1.5rem!important}.ps-sm-5{padding-left:3rem!important}.gap-sm-0{gap:0!important}.gap-sm-1{gap:.25rem!important}.gap-sm-2{gap:.5rem!important}.gap-sm-3{gap:1rem!important}.gap-sm-4{gap:1.5rem!important}.gap-sm-5{gap:3rem!important}.row-gap-sm-0{row-gap:0!important}.row-gap-sm-1{row-gap:.25rem!important}.row-gap-sm-2{row-gap:.5rem!important}.row-gap-sm-3{row-gap:1rem!important}.row-gap-sm-4{row-gap:1.5rem!important}.row-gap-sm-5{row-gap:3rem!important}.column-gap-sm-0{column-gap:0!important}.column-gap-sm-1{column-gap:.25rem!important}.column-gap-sm-2{column-gap:.5rem!important}.column-gap-sm-3{column-gap:1rem!important}.column-gap-sm-4{column-gap:1.5rem!important}.column-gap-sm-5{column-gap:3rem!important}.text-sm-start{text-align:left!important}.text-sm-end{text-align:right!important}.text-sm-center{text-align:center!important}}@media (width>=768px){.float-md-start{float:left!important}.float-md-end{float:right!important}.float-md-none{float:none!important}.object-fit-md-contain{object-fit:contain!important}.object-fit-md-cover{object-fit:cover!important}.object-fit-md-fill{object-fit:fill!important}.object-fit-md-scale{object-fit:scale-down!important}.object-fit-md-none{object-fit:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-grid{display:grid!important}.d-md-inline-grid{display:inline-grid!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}.d-md-none{display:none!important}.flex-md-fill{flex:auto!important}.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.justify-content-md-evenly{justify-content:space-evenly!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}.order-md-first{order:-1!important}.order-md-0{order:0!important}.order-md-1{order:1!important}.order-md-2{order:2!important}.order-md-3{order:3!important}.order-md-4{order:4!important}.order-md-5{order:5!important}.order-md-last{order:6!important}.m-md-0{margin:0!important}.m-md-1{margin:.25rem!important}.m-md-2{margin:.5rem!important}.m-md-3{margin:1rem!important}.m-md-4{margin:1.5rem!important}.m-md-5{margin:3rem!important}.m-md-auto{margin:auto!important}.mx-md-0{margin-left:0!important;margin-right:0!important}.mx-md-1{margin-left:.25rem!important;margin-right:.25rem!important}.mx-md-2{margin-left:.5rem!important;margin-right:.5rem!important}.mx-md-3{margin-left:1rem!important;margin-right:1rem!important}.mx-md-4{margin-left:1.5rem!important;margin-right:1.5rem!important}.mx-md-5{margin-left:3rem!important;margin-right:3rem!important}.mx-md-auto{margin-left:auto!important;margin-right:auto!important}.my-md-0{margin-top:0!important;margin-bottom:0!important}.my-md-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-md-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-md-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-md-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-md-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-md-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-md-0{margin-top:0!important}.mt-md-1{margin-top:.25rem!important}.mt-md-2{margin-top:.5rem!important}.mt-md-3{margin-top:1rem!important}.mt-md-4{margin-top:1.5rem!important}.mt-md-5{margin-top:3rem!important}.mt-md-auto{margin-top:auto!important}.me-md-0{margin-right:0!important}.me-md-1{margin-right:.25rem!important}.me-md-2{margin-right:.5rem!important}.me-md-3{margin-right:1rem!important}.me-md-4{margin-right:1.5rem!important}.me-md-5{margin-right:3rem!important}.me-md-auto{margin-right:auto!important}.mb-md-0{margin-bottom:0!important}.mb-md-1{margin-bottom:.25rem!important}.mb-md-2{margin-bottom:.5rem!important}.mb-md-3{margin-bottom:1rem!important}.mb-md-4{margin-bottom:1.5rem!important}.mb-md-5{margin-bottom:3rem!important}.mb-md-auto{margin-bottom:auto!important}.ms-md-0{margin-left:0!important}.ms-md-1{margin-left:.25rem!important}.ms-md-2{margin-left:.5rem!important}.ms-md-3{margin-left:1rem!important}.ms-md-4{margin-left:1.5rem!important}.ms-md-5{margin-left:3rem!important}.ms-md-auto{margin-left:auto!important}.p-md-0{padding:0!important}.p-md-1{padding:.25rem!important}.p-md-2{padding:.5rem!important}.p-md-3{padding:1rem!important}.p-md-4{padding:1.5rem!important}.p-md-5{padding:3rem!important}.px-md-0{padding-left:0!important;padding-right:0!important}.px-md-1{padding-left:.25rem!important;padding-right:.25rem!important}.px-md-2{padding-left:.5rem!important;padding-right:.5rem!important}.px-md-3{padding-left:1rem!important;padding-right:1rem!important}.px-md-4{padding-left:1.5rem!important;padding-right:1.5rem!important}.px-md-5{padding-left:3rem!important;padding-right:3rem!important}.py-md-0{padding-top:0!important;padding-bottom:0!important}.py-md-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-md-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-md-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-md-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-md-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-md-0{padding-top:0!important}.pt-md-1{padding-top:.25rem!important}.pt-md-2{padding-top:.5rem!important}.pt-md-3{padding-top:1rem!important}.pt-md-4{padding-top:1.5rem!important}.pt-md-5{padding-top:3rem!important}.pe-md-0{padding-right:0!important}.pe-md-1{padding-right:.25rem!important}.pe-md-2{padding-right:.5rem!important}.pe-md-3{padding-right:1rem!important}.pe-md-4{padding-right:1.5rem!important}.pe-md-5{padding-right:3rem!important}.pb-md-0{padding-bottom:0!important}.pb-md-1{padding-bottom:.25rem!important}.pb-md-2{padding-bottom:.5rem!important}.pb-md-3{padding-bottom:1rem!important}.pb-md-4{padding-bottom:1.5rem!important}.pb-md-5{padding-bottom:3rem!important}.ps-md-0{padding-left:0!important}.ps-md-1{padding-left:.25rem!important}.ps-md-2{padding-left:.5rem!important}.ps-md-3{padding-left:1rem!important}.ps-md-4{padding-left:1.5rem!important}.ps-md-5{padding-left:3rem!important}.gap-md-0{gap:0!important}.gap-md-1{gap:.25rem!important}.gap-md-2{gap:.5rem!important}.gap-md-3{gap:1rem!important}.gap-md-4{gap:1.5rem!important}.gap-md-5{gap:3rem!important}.row-gap-md-0{row-gap:0!important}.row-gap-md-1{row-gap:.25rem!important}.row-gap-md-2{row-gap:.5rem!important}.row-gap-md-3{row-gap:1rem!important}.row-gap-md-4{row-gap:1.5rem!important}.row-gap-md-5{row-gap:3rem!important}.column-gap-md-0{column-gap:0!important}.column-gap-md-1{column-gap:.25rem!important}.column-gap-md-2{column-gap:.5rem!important}.column-gap-md-3{column-gap:1rem!important}.column-gap-md-4{column-gap:1.5rem!important}.column-gap-md-5{column-gap:3rem!important}.text-md-start{text-align:left!important}.text-md-end{text-align:right!important}.text-md-center{text-align:center!important}}@media (width>=992px){.float-lg-start{float:left!important}.float-lg-end{float:right!important}.float-lg-none{float:none!important}.object-fit-lg-contain{object-fit:contain!important}.object-fit-lg-cover{object-fit:cover!important}.object-fit-lg-fill{object-fit:fill!important}.object-fit-lg-scale{object-fit:scale-down!important}.object-fit-lg-none{object-fit:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-grid{display:grid!important}.d-lg-inline-grid{display:inline-grid!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}.d-lg-none{display:none!important}.flex-lg-fill{flex:auto!important}.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.justify-content-lg-evenly{justify-content:space-evenly!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}.order-lg-first{order:-1!important}.order-lg-0{order:0!important}.order-lg-1{order:1!important}.order-lg-2{order:2!important}.order-lg-3{order:3!important}.order-lg-4{order:4!important}.order-lg-5{order:5!important}.order-lg-last{order:6!important}.m-lg-0{margin:0!important}.m-lg-1{margin:.25rem!important}.m-lg-2{margin:.5rem!important}.m-lg-3{margin:1rem!important}.m-lg-4{margin:1.5rem!important}.m-lg-5{margin:3rem!important}.m-lg-auto{margin:auto!important}.mx-lg-0{margin-left:0!important;margin-right:0!important}.mx-lg-1{margin-left:.25rem!important;margin-right:.25rem!important}.mx-lg-2{margin-left:.5rem!important;margin-right:.5rem!important}.mx-lg-3{margin-left:1rem!important;margin-right:1rem!important}.mx-lg-4{margin-left:1.5rem!important;margin-right:1.5rem!important}.mx-lg-5{margin-left:3rem!important;margin-right:3rem!important}.mx-lg-auto{margin-left:auto!important;margin-right:auto!important}.my-lg-0{margin-top:0!important;margin-bottom:0!important}.my-lg-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-lg-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-lg-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-lg-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-lg-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-lg-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-lg-0{margin-top:0!important}.mt-lg-1{margin-top:.25rem!important}.mt-lg-2{margin-top:.5rem!important}.mt-lg-3{margin-top:1rem!important}.mt-lg-4{margin-top:1.5rem!important}.mt-lg-5{margin-top:3rem!important}.mt-lg-auto{margin-top:auto!important}.me-lg-0{margin-right:0!important}.me-lg-1{margin-right:.25rem!important}.me-lg-2{margin-right:.5rem!important}.me-lg-3{margin-right:1rem!important}.me-lg-4{margin-right:1.5rem!important}.me-lg-5{margin-right:3rem!important}.me-lg-auto{margin-right:auto!important}.mb-lg-0{margin-bottom:0!important}.mb-lg-1{margin-bottom:.25rem!important}.mb-lg-2{margin-bottom:.5rem!important}.mb-lg-3{margin-bottom:1rem!important}.mb-lg-4{margin-bottom:1.5rem!important}.mb-lg-5{margin-bottom:3rem!important}.mb-lg-auto{margin-bottom:auto!important}.ms-lg-0{margin-left:0!important}.ms-lg-1{margin-left:.25rem!important}.ms-lg-2{margin-left:.5rem!important}.ms-lg-3{margin-left:1rem!important}.ms-lg-4{margin-left:1.5rem!important}.ms-lg-5{margin-left:3rem!important}.ms-lg-auto{margin-left:auto!important}.p-lg-0{padding:0!important}.p-lg-1{padding:.25rem!important}.p-lg-2{padding:.5rem!important}.p-lg-3{padding:1rem!important}.p-lg-4{padding:1.5rem!important}.p-lg-5{padding:3rem!important}.px-lg-0{padding-left:0!important;padding-right:0!important}.px-lg-1{padding-left:.25rem!important;padding-right:.25rem!important}.px-lg-2{padding-left:.5rem!important;padding-right:.5rem!important}.px-lg-3{padding-left:1rem!important;padding-right:1rem!important}.px-lg-4{padding-left:1.5rem!important;padding-right:1.5rem!important}.px-lg-5{padding-left:3rem!important;padding-right:3rem!important}.py-lg-0{padding-top:0!important;padding-bottom:0!important}.py-lg-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-lg-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-lg-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-lg-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-lg-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-lg-0{padding-top:0!important}.pt-lg-1{padding-top:.25rem!important}.pt-lg-2{padding-top:.5rem!important}.pt-lg-3{padding-top:1rem!important}.pt-lg-4{padding-top:1.5rem!important}.pt-lg-5{padding-top:3rem!important}.pe-lg-0{padding-right:0!important}.pe-lg-1{padding-right:.25rem!important}.pe-lg-2{padding-right:.5rem!important}.pe-lg-3{padding-right:1rem!important}.pe-lg-4{padding-right:1.5rem!important}.pe-lg-5{padding-right:3rem!important}.pb-lg-0{padding-bottom:0!important}.pb-lg-1{padding-bottom:.25rem!important}.pb-lg-2{padding-bottom:.5rem!important}.pb-lg-3{padding-bottom:1rem!important}.pb-lg-4{padding-bottom:1.5rem!important}.pb-lg-5{padding-bottom:3rem!important}.ps-lg-0{padding-left:0!important}.ps-lg-1{padding-left:.25rem!important}.ps-lg-2{padding-left:.5rem!important}.ps-lg-3{padding-left:1rem!important}.ps-lg-4{padding-left:1.5rem!important}.ps-lg-5{padding-left:3rem!important}.gap-lg-0{gap:0!important}.gap-lg-1{gap:.25rem!important}.gap-lg-2{gap:.5rem!important}.gap-lg-3{gap:1rem!important}.gap-lg-4{gap:1.5rem!important}.gap-lg-5{gap:3rem!important}.row-gap-lg-0{row-gap:0!important}.row-gap-lg-1{row-gap:.25rem!important}.row-gap-lg-2{row-gap:.5rem!important}.row-gap-lg-3{row-gap:1rem!important}.row-gap-lg-4{row-gap:1.5rem!important}.row-gap-lg-5{row-gap:3rem!important}.column-gap-lg-0{column-gap:0!important}.column-gap-lg-1{column-gap:.25rem!important}.column-gap-lg-2{column-gap:.5rem!important}.column-gap-lg-3{column-gap:1rem!important}.column-gap-lg-4{column-gap:1.5rem!important}.column-gap-lg-5{column-gap:3rem!important}.text-lg-start{text-align:left!important}.text-lg-end{text-align:right!important}.text-lg-center{text-align:center!important}}@media (width>=1200px){.float-xl-start{float:left!important}.float-xl-end{float:right!important}.float-xl-none{float:none!important}.object-fit-xl-contain{object-fit:contain!important}.object-fit-xl-cover{object-fit:cover!important}.object-fit-xl-fill{object-fit:fill!important}.object-fit-xl-scale{object-fit:scale-down!important}.object-fit-xl-none{object-fit:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-grid{display:grid!important}.d-xl-inline-grid{display:inline-grid!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}.d-xl-none{display:none!important}.flex-xl-fill{flex:auto!important}.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.justify-content-xl-evenly{justify-content:space-evenly!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}.order-xl-first{order:-1!important}.order-xl-0{order:0!important}.order-xl-1{order:1!important}.order-xl-2{order:2!important}.order-xl-3{order:3!important}.order-xl-4{order:4!important}.order-xl-5{order:5!important}.order-xl-last{order:6!important}.m-xl-0{margin:0!important}.m-xl-1{margin:.25rem!important}.m-xl-2{margin:.5rem!important}.m-xl-3{margin:1rem!important}.m-xl-4{margin:1.5rem!important}.m-xl-5{margin:3rem!important}.m-xl-auto{margin:auto!important}.mx-xl-0{margin-left:0!important;margin-right:0!important}.mx-xl-1{margin-left:.25rem!important;margin-right:.25rem!important}.mx-xl-2{margin-left:.5rem!important;margin-right:.5rem!important}.mx-xl-3{margin-left:1rem!important;margin-right:1rem!important}.mx-xl-4{margin-left:1.5rem!important;margin-right:1.5rem!important}.mx-xl-5{margin-left:3rem!important;margin-right:3rem!important}.mx-xl-auto{margin-left:auto!important;margin-right:auto!important}.my-xl-0{margin-top:0!important;margin-bottom:0!important}.my-xl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xl-0{margin-top:0!important}.mt-xl-1{margin-top:.25rem!important}.mt-xl-2{margin-top:.5rem!important}.mt-xl-3{margin-top:1rem!important}.mt-xl-4{margin-top:1.5rem!important}.mt-xl-5{margin-top:3rem!important}.mt-xl-auto{margin-top:auto!important}.me-xl-0{margin-right:0!important}.me-xl-1{margin-right:.25rem!important}.me-xl-2{margin-right:.5rem!important}.me-xl-3{margin-right:1rem!important}.me-xl-4{margin-right:1.5rem!important}.me-xl-5{margin-right:3rem!important}.me-xl-auto{margin-right:auto!important}.mb-xl-0{margin-bottom:0!important}.mb-xl-1{margin-bottom:.25rem!important}.mb-xl-2{margin-bottom:.5rem!important}.mb-xl-3{margin-bottom:1rem!important}.mb-xl-4{margin-bottom:1.5rem!important}.mb-xl-5{margin-bottom:3rem!important}.mb-xl-auto{margin-bottom:auto!important}.ms-xl-0{margin-left:0!important}.ms-xl-1{margin-left:.25rem!important}.ms-xl-2{margin-left:.5rem!important}.ms-xl-3{margin-left:1rem!important}.ms-xl-4{margin-left:1.5rem!important}.ms-xl-5{margin-left:3rem!important}.ms-xl-auto{margin-left:auto!important}.p-xl-0{padding:0!important}.p-xl-1{padding:.25rem!important}.p-xl-2{padding:.5rem!important}.p-xl-3{padding:1rem!important}.p-xl-4{padding:1.5rem!important}.p-xl-5{padding:3rem!important}.px-xl-0{padding-left:0!important;padding-right:0!important}.px-xl-1{padding-left:.25rem!important;padding-right:.25rem!important}.px-xl-2{padding-left:.5rem!important;padding-right:.5rem!important}.px-xl-3{padding-left:1rem!important;padding-right:1rem!important}.px-xl-4{padding-left:1.5rem!important;padding-right:1.5rem!important}.px-xl-5{padding-left:3rem!important;padding-right:3rem!important}.py-xl-0{padding-top:0!important;padding-bottom:0!important}.py-xl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xl-0{padding-top:0!important}.pt-xl-1{padding-top:.25rem!important}.pt-xl-2{padding-top:.5rem!important}.pt-xl-3{padding-top:1rem!important}.pt-xl-4{padding-top:1.5rem!important}.pt-xl-5{padding-top:3rem!important}.pe-xl-0{padding-right:0!important}.pe-xl-1{padding-right:.25rem!important}.pe-xl-2{padding-right:.5rem!important}.pe-xl-3{padding-right:1rem!important}.pe-xl-4{padding-right:1.5rem!important}.pe-xl-5{padding-right:3rem!important}.pb-xl-0{padding-bottom:0!important}.pb-xl-1{padding-bottom:.25rem!important}.pb-xl-2{padding-bottom:.5rem!important}.pb-xl-3{padding-bottom:1rem!important}.pb-xl-4{padding-bottom:1.5rem!important}.pb-xl-5{padding-bottom:3rem!important}.ps-xl-0{padding-left:0!important}.ps-xl-1{padding-left:.25rem!important}.ps-xl-2{padding-left:.5rem!important}.ps-xl-3{padding-left:1rem!important}.ps-xl-4{padding-left:1.5rem!important}.ps-xl-5{padding-left:3rem!important}.gap-xl-0{gap:0!important}.gap-xl-1{gap:.25rem!important}.gap-xl-2{gap:.5rem!important}.gap-xl-3{gap:1rem!important}.gap-xl-4{gap:1.5rem!important}.gap-xl-5{gap:3rem!important}.row-gap-xl-0{row-gap:0!important}.row-gap-xl-1{row-gap:.25rem!important}.row-gap-xl-2{row-gap:.5rem!important}.row-gap-xl-3{row-gap:1rem!important}.row-gap-xl-4{row-gap:1.5rem!important}.row-gap-xl-5{row-gap:3rem!important}.column-gap-xl-0{column-gap:0!important}.column-gap-xl-1{column-gap:.25rem!important}.column-gap-xl-2{column-gap:.5rem!important}.column-gap-xl-3{column-gap:1rem!important}.column-gap-xl-4{column-gap:1.5rem!important}.column-gap-xl-5{column-gap:3rem!important}.text-xl-start{text-align:left!important}.text-xl-end{text-align:right!important}.text-xl-center{text-align:center!important}}@media (width>=1400px){.float-xxl-start{float:left!important}.float-xxl-end{float:right!important}.float-xxl-none{float:none!important}.object-fit-xxl-contain{object-fit:contain!important}.object-fit-xxl-cover{object-fit:cover!important}.object-fit-xxl-fill{object-fit:fill!important}.object-fit-xxl-scale{object-fit:scale-down!important}.object-fit-xxl-none{object-fit:none!important}.d-xxl-inline{display:inline!important}.d-xxl-inline-block{display:inline-block!important}.d-xxl-block{display:block!important}.d-xxl-grid{display:grid!important}.d-xxl-inline-grid{display:inline-grid!important}.d-xxl-table{display:table!important}.d-xxl-table-row{display:table-row!important}.d-xxl-table-cell{display:table-cell!important}.d-xxl-flex{display:flex!important}.d-xxl-inline-flex{display:inline-flex!important}.d-xxl-none{display:none!important}.flex-xxl-fill{flex:auto!important}.flex-xxl-row{flex-direction:row!important}.flex-xxl-column{flex-direction:column!important}.flex-xxl-row-reverse{flex-direction:row-reverse!important}.flex-xxl-column-reverse{flex-direction:column-reverse!important}.flex-xxl-grow-0{flex-grow:0!important}.flex-xxl-grow-1{flex-grow:1!important}.flex-xxl-shrink-0{flex-shrink:0!important}.flex-xxl-shrink-1{flex-shrink:1!important}.flex-xxl-wrap{flex-wrap:wrap!important}.flex-xxl-nowrap{flex-wrap:nowrap!important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-xxl-start{justify-content:flex-start!important}.justify-content-xxl-end{justify-content:flex-end!important}.justify-content-xxl-center{justify-content:center!important}.justify-content-xxl-between{justify-content:space-between!important}.justify-content-xxl-around{justify-content:space-around!important}.justify-content-xxl-evenly{justify-content:space-evenly!important}.align-items-xxl-start{align-items:flex-start!important}.align-items-xxl-end{align-items:flex-end!important}.align-items-xxl-center{align-items:center!important}.align-items-xxl-baseline{align-items:baseline!important}.align-items-xxl-stretch{align-items:stretch!important}.align-content-xxl-start{align-content:flex-start!important}.align-content-xxl-end{align-content:flex-end!important}.align-content-xxl-center{align-content:center!important}.align-content-xxl-between{align-content:space-between!important}.align-content-xxl-around{align-content:space-around!important}.align-content-xxl-stretch{align-content:stretch!important}.align-self-xxl-auto{align-self:auto!important}.align-self-xxl-start{align-self:flex-start!important}.align-self-xxl-end{align-self:flex-end!important}.align-self-xxl-center{align-self:center!important}.align-self-xxl-baseline{align-self:baseline!important}.align-self-xxl-stretch{align-self:stretch!important}.order-xxl-first{order:-1!important}.order-xxl-0{order:0!important}.order-xxl-1{order:1!important}.order-xxl-2{order:2!important}.order-xxl-3{order:3!important}.order-xxl-4{order:4!important}.order-xxl-5{order:5!important}.order-xxl-last{order:6!important}.m-xxl-0{margin:0!important}.m-xxl-1{margin:.25rem!important}.m-xxl-2{margin:.5rem!important}.m-xxl-3{margin:1rem!important}.m-xxl-4{margin:1.5rem!important}.m-xxl-5{margin:3rem!important}.m-xxl-auto{margin:auto!important}.mx-xxl-0{margin-left:0!important;margin-right:0!important}.mx-xxl-1{margin-left:.25rem!important;margin-right:.25rem!important}.mx-xxl-2{margin-left:.5rem!important;margin-right:.5rem!important}.mx-xxl-3{margin-left:1rem!important;margin-right:1rem!important}.mx-xxl-4{margin-left:1.5rem!important;margin-right:1.5rem!important}.mx-xxl-5{margin-left:3rem!important;margin-right:3rem!important}.mx-xxl-auto{margin-left:auto!important;margin-right:auto!important}.my-xxl-0{margin-top:0!important;margin-bottom:0!important}.my-xxl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xxl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xxl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xxl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xxl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xxl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xxl-0{margin-top:0!important}.mt-xxl-1{margin-top:.25rem!important}.mt-xxl-2{margin-top:.5rem!important}.mt-xxl-3{margin-top:1rem!important}.mt-xxl-4{margin-top:1.5rem!important}.mt-xxl-5{margin-top:3rem!important}.mt-xxl-auto{margin-top:auto!important}.me-xxl-0{margin-right:0!important}.me-xxl-1{margin-right:.25rem!important}.me-xxl-2{margin-right:.5rem!important}.me-xxl-3{margin-right:1rem!important}.me-xxl-4{margin-right:1.5rem!important}.me-xxl-5{margin-right:3rem!important}.me-xxl-auto{margin-right:auto!important}.mb-xxl-0{margin-bottom:0!important}.mb-xxl-1{margin-bottom:.25rem!important}.mb-xxl-2{margin-bottom:.5rem!important}.mb-xxl-3{margin-bottom:1rem!important}.mb-xxl-4{margin-bottom:1.5rem!important}.mb-xxl-5{margin-bottom:3rem!important}.mb-xxl-auto{margin-bottom:auto!important}.ms-xxl-0{margin-left:0!important}.ms-xxl-1{margin-left:.25rem!important}.ms-xxl-2{margin-left:.5rem!important}.ms-xxl-3{margin-left:1rem!important}.ms-xxl-4{margin-left:1.5rem!important}.ms-xxl-5{margin-left:3rem!important}.ms-xxl-auto{margin-left:auto!important}.p-xxl-0{padding:0!important}.p-xxl-1{padding:.25rem!important}.p-xxl-2{padding:.5rem!important}.p-xxl-3{padding:1rem!important}.p-xxl-4{padding:1.5rem!important}.p-xxl-5{padding:3rem!important}.px-xxl-0{padding-left:0!important;padding-right:0!important}.px-xxl-1{padding-left:.25rem!important;padding-right:.25rem!important}.px-xxl-2{padding-left:.5rem!important;padding-right:.5rem!important}.px-xxl-3{padding-left:1rem!important;padding-right:1rem!important}.px-xxl-4{padding-left:1.5rem!important;padding-right:1.5rem!important}.px-xxl-5{padding-left:3rem!important;padding-right:3rem!important}.py-xxl-0{padding-top:0!important;padding-bottom:0!important}.py-xxl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xxl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xxl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xxl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xxl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xxl-0{padding-top:0!important}.pt-xxl-1{padding-top:.25rem!important}.pt-xxl-2{padding-top:.5rem!important}.pt-xxl-3{padding-top:1rem!important}.pt-xxl-4{padding-top:1.5rem!important}.pt-xxl-5{padding-top:3rem!important}.pe-xxl-0{padding-right:0!important}.pe-xxl-1{padding-right:.25rem!important}.pe-xxl-2{padding-right:.5rem!important}.pe-xxl-3{padding-right:1rem!important}.pe-xxl-4{padding-right:1.5rem!important}.pe-xxl-5{padding-right:3rem!important}.pb-xxl-0{padding-bottom:0!important}.pb-xxl-1{padding-bottom:.25rem!important}.pb-xxl-2{padding-bottom:.5rem!important}.pb-xxl-3{padding-bottom:1rem!important}.pb-xxl-4{padding-bottom:1.5rem!important}.pb-xxl-5{padding-bottom:3rem!important}.ps-xxl-0{padding-left:0!important}.ps-xxl-1{padding-left:.25rem!important}.ps-xxl-2{padding-left:.5rem!important}.ps-xxl-3{padding-left:1rem!important}.ps-xxl-4{padding-left:1.5rem!important}.ps-xxl-5{padding-left:3rem!important}.gap-xxl-0{gap:0!important}.gap-xxl-1{gap:.25rem!important}.gap-xxl-2{gap:.5rem!important}.gap-xxl-3{gap:1rem!important}.gap-xxl-4{gap:1.5rem!important}.gap-xxl-5{gap:3rem!important}.row-gap-xxl-0{row-gap:0!important}.row-gap-xxl-1{row-gap:.25rem!important}.row-gap-xxl-2{row-gap:.5rem!important}.row-gap-xxl-3{row-gap:1rem!important}.row-gap-xxl-4{row-gap:1.5rem!important}.row-gap-xxl-5{row-gap:3rem!important}.column-gap-xxl-0{column-gap:0!important}.column-gap-xxl-1{column-gap:.25rem!important}.column-gap-xxl-2{column-gap:.5rem!important}.column-gap-xxl-3{column-gap:1rem!important}.column-gap-xxl-4{column-gap:1.5rem!important}.column-gap-xxl-5{column-gap:3rem!important}.text-xxl-start{text-align:left!important}.text-xxl-end{text-align:right!important}.text-xxl-center{text-align:center!important}}@media (width>=1200px){.fs-1{font-size:2.5rem!important}.fs-2{font-size:2rem!important}.fs-3{font-size:1.75rem!important}.fs-4{font-size:1.5rem!important}}@media print{.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-grid{display:grid!important}.d-print-inline-grid{display:inline-grid!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}.d-print-none{display:none!important}}html[data-theme=light]{--bg:#fbf9f4;--surface:#fff;--surface-2:#f8f6f2;--border:#e3ddcf;--border-soft:#ebe6da;--text:#0b0b0b;--text-muted:#5b564d;--text-dim:#8a8478;--brand-red:#c71f38;--amber:#c71f38;--teal:#a33c4b;--periwinkle:#4a463e;--rose:#c97c89;--sage:#8c1729;--amber-dim:#c71f381f;--teal-dim:#a33c4b1f;--nav-bg:#fbf9f4db;--on-accent:#fbf6f0;--state-active:#2e7d64;--state-active-dim:#2e7d641f;--state-maintenance:#96620a;--state-maintenance-dim:#96620a1f;--state-security:#c71f38;--state-security-dim:#c71f381f;--state-archived:#6b665c;--state-archived-dim:#6b665c1a}html[data-theme=dark]{--bg:#0b0b0b;--surface:#161616;--surface-2:#1e1e1e;--border:#2c2c2c;--border-soft:#232323;--text:#f1eee8;--text-muted:#9a948d;--text-dim:#66605a;--brand-red:#c71f38;--amber:#da3550;--teal:#e8637a;--periwinkle:#5c5854;--rose:#f0a9b6;--sage:#8c1729;--amber-dim:#da355029;--teal-dim:#e8637a29;--nav-bg:#0b0b0bdb;--state-active:#58b79a;--state-active-dim:#58b79a29;--state-maintenance:#dca23c;--state-maintenance-dim:#dca23c29;--state-security:#e8637a;--state-security-dim:#e8637a29;--state-archived:#8a847c;--state-archived-dim:#8a847c24}html{background:var(--bg);scroll-behavior:smooth;--maxw:1180px;--radius:12px;--mono:"Space Grotesk", sans-serif;--sans:"Barlow", -apple-system, sans-serif;transition:background .2s}body{background:var(--bg);color:var(--text);font-family:var(--sans);-webkit-font-smoothing:antialiased;margin:0;font-size:16px;line-height:1.6;transition:background .2s,color .2s}*{box-sizing:border-box;transition:background-color .2s,border-color .2s,color .2s}a{color:inherit;text-decoration:none}img{max-width:100%;display:block}.wrap{max-width:var(--maxw);margin:0 auto;padding:0}.eyebrow{font-family:var(--mono);letter-spacing:.14em;text-transform:uppercase;color:var(--teal);align-items:center;gap:8px;font-size:12px;display:inline-flex}.eyebrow:before{content:"";background:var(--teal);width:6px;height:6px;box-shadow:0 0 0 3px var(--teal-dim);border-radius:50%}h1,h2,h3,h4{font-family:var(--mono);color:var(--text);letter-spacing:-.01em;margin:0;font-weight:600}p{color:var(--text-muted);margin:0}section{padding:88px 0}.section-head{max-width:640px;margin-bottom:48px}.section-head h2{margin-top:14px;font-size:30px}.section-head p{margin-top:14px;font-size:16.5px}.core-band{background:var(--surface-2);border:1px solid var(--border-soft);border-left:3px solid var(--teal);border-radius:var(--radius);margin-bottom:40px;margin-left:0;margin-right:0;padding:32px 40px}@media (width<=991.98px){.core-band .border-start{margin-top:16px;border:none!important;padding-left:0!important}}.core-band-label{font-family:var(--mono);letter-spacing:.14em;text-transform:uppercase;color:var(--teal);align-items:center;gap:8px;font-size:12px;display:inline-flex;margin-bottom:14px!important}.core-band-label:before{content:"";background:var(--teal);width:6px;height:6px;box-shadow:0 0 0 3px var(--teal-dim);border-radius:50%}.core-band code{font-family:var(--mono);color:var(--teal);background:var(--teal-dim);border:1px solid var(--teal);border-radius:6px;margin:0 8px 10px 0;padding:5px 12px;font-size:13px;display:inline-block}.core-band p{margin:0;font-size:15.5px;line-height:1.75}.tag-badge{font-family:var(--mono);color:var(--teal);background:var(--teal-dim);border:1px solid var(--teal);border-radius:6px;padding:5px 12px;font-size:13px;text-decoration:none;display:inline-block}.tag-badge:hover{background:var(--teal);color:var(--on-accent)}header{z-index:50;background:var(--nav-bg);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border-bottom:1px solid var(--border-soft);position:sticky;top:0}.logo{align-items:center;display:flex}.logo-full{flex-shrink:0;width:auto;height:26px}.navbar{min-height:72px;padding-top:0;padding-bottom:0}.navbar-nav .nav-link{color:var(--text-muted);white-space:nowrap;font-size:14.5px;transition:color .15s}.navbar-nav .nav-link:hover{color:var(--text)}@media (width>=992px){.navbar-nav{gap:30px}.navbar-nav .nav-link{padding-left:0;padding-right:0}}.navbar-nav .dropdown-menu{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);min-width:190px;padding:6px}.navbar-nav .dropdown-menu .dropdown-item{color:var(--text-muted);border-radius:calc(var(--radius) / 2);padding:8px 12px;font-size:14.5px;transition:background .15s,color .15s}.navbar-nav .dropdown-menu .dropdown-item:hover,.navbar-nav .dropdown-menu .dropdown-item:focus{background:var(--surface-2);color:var(--text)}.navbar-nav .dropdown-toggle:after{vertical-align:.15em;margin-left:.4em}.nav-cta{font-family:var(--mono);border:1px solid var(--amber);border-radius:999px;font-size:13.5px;font-weight:500;transition:background .15s,color .15s;color:var(--amber)!important;padding:9px 18px!important}.nav-cta:hover{background:var(--amber);color:var(--on-accent)!important}.navbar-toggler{border:none;padding:8px}.navbar-toggler:focus{box-shadow:none}html[data-theme=light] .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba(11,11,11,1)' stroke-width='2' stroke-linecap='round' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}html[data-theme=dark] .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba(241,238,232,1)' stroke-width='2' stroke-linecap='round' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}@media (width<=991.98px){:is(header:has(.navbar-collapse.show),header:has(.navbar-collapse.collapsing)){background:var(--bg);-webkit-backdrop-filter:none;backdrop-filter:none}.navbar-collapse{background:var(--bg);border-top:1px solid var(--border-soft);margin:0 -28px;padding:0 28px}.navbar-nav{padding:10px 0 20px}.navbar-nav .nav-link{border-bottom:1px solid var(--border-soft);padding:14px 0;font-size:16px}.navbar-nav .dropdown-menu{border:0;border-bottom:1px solid var(--border-soft);background:0 0;border-radius:0;margin:0;padding:0 0 6px 14px}.navbar-nav .dropdown-menu .dropdown-item{padding:11px 0;font-size:15px}.navbar-nav .dropdown-menu .dropdown-item:hover,.navbar-nav .dropdown-menu .dropdown-item:focus{background:0 0}.navbar-nav .nav-cta{text-align:center;border:1px solid var(--amber);border-radius:999px;width:100%;margin-top:16px;padding:12px 18px;font-size:14.5px;transition:background .15s,color .15s;display:block;color:var(--amber)!important}}.theme-toggle{border:1px solid var(--border);width:38px;height:38px;color:var(--text-muted);cursor:pointer;background:0 0;border-radius:9px;flex-shrink:0;justify-content:center;align-items:center;transition:border-color .15s,color .15s;display:flex}.theme-toggle:hover{border-color:var(--text-muted);color:var(--text)}.theme-toggle svg{width:18px;height:18px}.theme-toggle .icon-moon,html[data-theme=light] .theme-toggle .icon-sun{display:none}html[data-theme=light] .theme-toggle .icon-moon{display:block}.btn{font-family:var(--mono);border:1px solid var(--border);white-space:nowrap;border-radius:8px;align-items:center;gap:8px;padding:11px 20px;font-size:13.5px;font-weight:500;transition:border-color .15s,background .15s,transform .15s;display:inline-flex}.btn:hover{transform:translateY(-1px)}.btn svg{width:15px;height:15px}.btn-primary{background:var(--amber);color:var(--on-accent);border-color:var(--amber);font-weight:600}.btn-primary:hover{background:var(--amber);box-shadow:0 6px 20px var(--amber-dim)}.btn-ghost{color:var(--text)}.btn-ghost:hover{border-color:var(--text-muted)}.btn-sm{border-radius:7px;padding:8px 14px;font-size:12.5px}.hero{padding:76px 0 40px}.hero h1{letter-spacing:-.02em;margin-top:18px;font-size:44px;line-height:1.15}.hero p.sub{max-width:520px;color:var(--text-muted);margin-top:20px;font-size:18px}.hero-grid{grid-template-columns:1.05fr .95fr;align-items:center;gap:64px;display:grid}.hero-ctas{flex-wrap:wrap;gap:14px;margin-top:32px;display:flex}.hero .md-actions-bar,.page-intro .md-actions-bar{justify-content:flex-start;margin-top:16px;margin-bottom:0}.hero-stack-note{font-family:var(--mono);color:var(--text-dim);flex-wrap:wrap;gap:18px;margin-top:34px;font-size:12.5px;display:flex}.hero-stack-note span{color:var(--text-muted)}.hero-visual{flex-direction:column;gap:18px;display:flex}.hero-art-row{margin-top:46px}@media (width<=980px){.hero-art-row{margin-top:36px}}.hero-art{background:var(--surface);border:1px solid var(--border);color:var(--text-dim);border-radius:16px;margin:0;padding:22px 24px 18px;position:relative}.hero-art figcaption{font-family:var(--mono);letter-spacing:.12em;text-transform:uppercase;color:var(--text-dim);margin-bottom:14px;font-size:11px}.hero-art svg{stroke:currentColor;fill:none;stroke-width:1.25px;stroke-linecap:round;stroke-linejoin:round;width:100%;height:auto;display:block;overflow:visible}.hero-art .art-accent{stroke:var(--teal)}.hero-art .art-hot{stroke:var(--brand-red)}.hero-art .art-fill{fill:var(--teal-dim);stroke:var(--teal)}.hero-art .art-dot{fill:var(--teal);stroke:none}.hero-art .art-dash{stroke-dasharray:4 4;opacity:.75}.hero-art .art-faint{opacity:.45}.pipeline{background:var(--surface);border:1px solid var(--border);border-radius:16px;padding:26px 26px 30px;position:relative}.pipeline-label{font-family:var(--mono);letter-spacing:.12em;text-transform:uppercase;color:var(--text-dim);justify-content:space-between;margin-bottom:18px;font-size:11px;display:flex}.pipeline-track{padding-left:28px;position:relative}.pipeline-track:before{content:"";background:linear-gradient(180deg, var(--sage), var(--brand-red));opacity:.5;width:2px;position:absolute;top:10px;bottom:10px;left:9px}.pl-step{background:var(--surface-2);border:1px solid var(--border-soft);border-radius:9px;justify-content:space-between;align-items:center;gap:10px;margin-bottom:10px;padding:13px 16px;display:flex;position:relative}.pl-step:last-child{margin-bottom:0}.pl-step:before{content:"";background:var(--bg);border:2px solid var(--teal);border-radius:50%;width:9px;height:9px;position:absolute;top:50%;left:-23px;transform:translateY(-50%)}.pl-step .name{font-family:var(--mono);color:var(--text);font-size:13.5px;font-weight:500}.pl-step .tag{font-family:var(--mono);color:var(--text-dim);font-size:10.5px}.pl-packet{background:var(--brand-red);border-radius:50%;width:14px;height:14px;animation:5s ease-in-out infinite travel;position:absolute;left:2px;box-shadow:0 0 12px 2px #c71f3838}@keyframes travel{0%{opacity:0;top:8px}6%{opacity:1}46%{opacity:1;top:calc(100% - 22px)}50%{opacity:0}50.01%{top:8px}56%{opacity:1}96%{opacity:1;top:calc(100% - 22px)}to{opacity:0;top:calc(100% - 22px)}}@media (prefers-reduced-motion:reduce){.pl-packet{opacity:1;animation:none;top:8px}html{scroll-behavior:auto}}.split{grid-template-columns:1.1fr .9fr;align-items:start;gap:64px;display:grid}.split h2{font-size:28px}.split .lede{color:var(--text-muted);margin-top:16px;font-size:17px;line-height:1.7}.extending{font-family:var(--mono);letter-spacing:.1em;text-transform:uppercase;color:var(--text-dim);align-items:center;gap:12px;margin-top:26px;font-size:12px;display:flex}.extending .extending-logo{text-transform:none;align-items:center;display:flex}.extending .extending-logo img{width:auto;height:36px;display:block}.extending .lockup{font-family:var(--mono);color:var(--text);text-transform:none;letter-spacing:0;font-size:15px;font-weight:600}.extending .lockup .x{color:var(--text-dim);margin:0 6px;font-weight:400}.traits{grid-template-columns:1fr 1fr;gap:14px;display:grid}.trait{background:var(--surface);border:1px solid var(--border);text-align:center;border-radius:12px;padding:22px 18px}.trait .icon{margin-bottom:12px}.trait p{font-family:var(--mono);color:var(--text);font-size:13px;font-weight:500;line-height:1.4}.products-grid{grid-template-columns:repeat(3,1fr);gap:18px;display:grid}.card{background:var(--surface);border:1px solid var(--border);border-top:3px solid var(--accent,var(--teal));border-radius:var(--radius);flex-direction:column;height:100%;padding:26px 24px 24px;display:flex}.card .card-top{justify-content:space-between;align-items:flex-start;margin-bottom:14px;display:flex}.card h3{color:var(--text);font-size:20px}.card .kicker{font-family:var(--mono);color:var(--text-dim);letter-spacing:.1em;text-transform:uppercase;margin-bottom:6px;font-size:10.5px;display:block}.card p.lede{color:var(--text);margin-bottom:10px;font-size:15px;font-weight:500;line-height:1.5}.card p.desc{flex-grow:1;font-size:14.5px;line-height:1.65}.card .card-btns{flex-wrap:wrap;gap:8px;margin-top:20px;display:flex}.badges{flex-wrap:wrap;gap:5px;margin-bottom:16px;display:flex}.badges img{border-radius:3px;height:19px}.packages-grid{grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:18px;display:grid}.packages-divider{border:none;border-top:1px solid var(--border);grid-column:1/-1;margin:4px 0}.package-card{border-top-width:4px;transition:box-shadow .2s,transform .2s}.package-card:hover{box-shadow:0 6px 20px var(--accent-dim);transform:translateY(-2px)}.package-card .card-top{margin-bottom:12px}.package-card h3{margin:0}.package-state{border:1px solid var(--accent);background:var(--accent-dim);color:var(--accent);font-family:var(--mono);letter-spacing:.1em;text-transform:uppercase;border-radius:999px;align-items:center;gap:7px;margin-bottom:8px;padding:4px 10px;font-size:10.5px;font-weight:600;display:inline-flex}.package-state:before{content:"";background:var(--accent);border-radius:50%;flex:none;width:7px;height:7px}.package-card--active{--accent:var(--state-active);--accent-dim:var(--state-active-dim)}.package-card--maintenance{--accent:var(--state-maintenance);--accent-dim:var(--state-maintenance-dim)}.package-card--maintenance .package-state:before{box-shadow:inset 0 0 0 2px var(--accent);background:0 0}.package-card--security-only{--accent:var(--state-security);--accent-dim:var(--state-security-dim)}.package-card--security-only .package-state:before{box-shadow:0 0 0 3px var(--accent-dim)}.package-card--archived,.package-card--unknown{--accent:var(--state-archived);--accent-dim:var(--state-archived-dim);border-style:solid dashed dashed}.package-card--archived h3,.package-card--unknown h3{color:var(--text-muted)}.package-card--archived .package-state:before,.package-card--unknown .package-state:before{box-shadow:inset 0 0 0 2px var(--accent);background:0 0;border-radius:2px}.package-card--archived .badges img,.package-card--unknown .badges img{opacity:.65}.package-description{color:var(--text-muted);margin:0 0 14px;font-size:14px}.package-archived{color:var(--text-dim);margin:0;font-size:12px}.packages-generated-at{color:var(--text-dim);margin-top:22px}@media (prefers-reduced-motion:reduce){.package-card{transition:none}.package-card:hover{transform:none}}.standards-grid{background:var(--border);border:1px solid var(--border);border-radius:var(--radius);grid-template-columns:repeat(3,1fr);gap:1px;display:grid;overflow:hidden}.standard{background:var(--surface);padding:30px 26px}.standard h4{color:var(--text);margin-bottom:10px;font-size:16px}.standard h4 a{color:inherit;border-bottom:1px solid var(--border)}.standard h4 a:hover{color:var(--amber);border-color:var(--amber)}.standard p{font-size:14px;line-height:1.65}.standard .get-started{font-family:var(--mono);color:var(--text-dim);margin-top:14px;font-size:12px}.standard .get-started a{color:var(--teal)}.standard .get-started a:hover{text-decoration:underline}.components-grid{grid-template-columns:1fr 1fr;gap:0 40px;display:grid}details.acc{border-bottom:1px solid var(--border-soft)}details.acc summary{cursor:pointer;font-family:var(--mono);color:var(--text);justify-content:space-between;align-items:center;padding:18px 0;font-size:15px;font-weight:500;list-style:none;display:flex}details.acc summary::-webkit-details-marker{display:none}details.acc summary .chev{color:var(--text-dim);font-size:18px;transition:transform .2s}details.acc[open] summary .chev{color:var(--amber);transform:rotate(45deg)}.acc-body{padding:0 0 20px}.pkg-row{border-top:1px solid var(--border-soft);justify-content:space-between;gap:16px;padding:9px 0;display:flex}.pkg-row:first-child{border-top:none}.pkg-row a{font-family:var(--mono);color:var(--teal);white-space:nowrap;font-size:13.5px}.pkg-row a:hover{text-decoration:underline}.pkg-row .pkg-desc{color:var(--text-muted);text-align:right;font-size:13px}.posts-grid{grid-template-columns:repeat(3,1fr);gap:20px;display:grid}.posts-grid-2-col{grid-template-columns:repeat(2,1fr);gap:20px;display:grid}.post-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:24px;transition:border-color .15s}.post-card:hover{border-color:var(--text-dim)}.post-card h4{color:var(--text);margin-top:10px;font-size:16.5px;line-height:1.4}.post-card p{margin-top:10px;font-size:13.5px;line-height:1.6}.post-card .read-more{font-family:var(--mono);color:var(--amber);margin-top:14px;margin-left:2px;font-size:12px;display:inline-block}.post-date{font-family:var(--mono);color:var(--text-dim);letter-spacing:.06em;text-transform:uppercase;font-size:11.5px}.about-band{background:var(--surface);border-top:1px solid var(--border-soft);border-bottom:1px solid var(--border-soft)}.about-inner{grid-template-columns:1fr 1.3fr;align-items:center;gap:56px;display:grid}.about-inner h2{margin-top:14px;font-size:26px;line-height:1.3}.about-inner .about-text{font-size:15px;line-height:1.75}.about-inner .about-cta{margin-top:22px}footer{padding:50px 0 40px}.footer-nav{border-bottom:1px solid var(--border-soft);grid-template-columns:repeat(3,minmax(0,1fr));gap:30px;margin-bottom:34px;padding-bottom:34px;display:grid}.footer-nav ul{margin:0;padding:0;list-style:none}.footer-nav li+li{margin-top:10px}.footer-nav a{color:var(--text-muted);font-size:13.5px;text-decoration:none;transition:color .15s}.footer-nav a:hover{color:var(--text)}.footer-col-title{font-family:var(--mono);letter-spacing:.12em;text-transform:uppercase;color:var(--text-dim);margin:0 0 14px;font-size:11px}@media (width<=700px){.footer-nav{grid-template-columns:1fr;gap:26px}}.footer-top{border-bottom:1px solid var(--border-soft);flex-wrap:wrap;justify-content:space-between;align-items:flex-start;gap:30px;padding-bottom:34px;display:flex}.footer-copy{color:var(--text-dim);max-width:460px;font-size:13px;line-height:1.8}.footer-copy a{color:var(--text-muted);border-bottom:1px solid var(--border)}.footer-social{gap:12px;display:flex}.footer-social a{border:1px solid var(--border);width:38px;height:38px;color:var(--text);border-radius:9px;justify-content:center;align-items:center;transition:border-color .15s,color .15s;display:flex}.footer-social a:hover{border-color:var(--teal);color:var(--teal)}.footer-social svg{width:17px;height:17px}.footer-bottom{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:14px;padding-top:24px;display:flex}.footer-bottom .sponsor{font-family:var(--mono);color:var(--text-dim);font-size:12px}.footer-bottom .sponsor a{color:var(--text-muted);border-bottom:1px solid var(--border)}@media (width<=980px){.hero-grid{grid-template-columns:1fr;gap:44px}.hero{padding-top:44px}.split{grid-template-columns:1fr;gap:40px}.products-grid,.standards-grid{grid-template-columns:repeat(2,1fr)}.posts-grid{grid-template-columns:1fr}.about-inner{grid-template-columns:1fr;gap:26px}}@media (width<=700px){.wrap{padding:0 16px}.hero h1{font-size:32px}.products-grid,.standards-grid,.components-grid{grid-template-columns:1fr}section{padding:56px 0}.pkg-row{flex-direction:column;gap:2px}.pkg-row .pkg-desc{text-align:left}}:focus-visible{outline:2px solid var(--amber);outline-offset:2px}.icon{flex-shrink:0;width:18px;height:18px}.icon-lg{width:50px;height:50px;margin:auto}.icon-jetbrains{min-width:100px}[data-theme=light] .icon{filter:none}[data-theme=dark] .icon{filter:invert()}.contact-grid{grid-template-columns:1fr 1fr;gap:18px;display:grid}@media (width<=700px){.contact-grid{grid-template-columns:1fr}}.contact-card{text-align:center;align-items:center}.contact-card svg{width:40px;height:40px;color:var(--teal);margin-bottom:14px}.contact-card .card-btns{justify-content:center}.approach-card{margin-top:18px}.approach-card h3{margin-bottom:10px}.latest-posts{margin-top:48px}.latest-posts .section-head{margin-bottom:24px}.latest-posts .posts-grid{grid-template-columns:repeat(2,1fr)}@media (width<=700px){.latest-posts .posts-grid{grid-template-columns:1fr}}.sidebar{flex-direction:column;gap:34px;display:flex}@media (width<=900px){.sidebar{order:2}}.widget{border-bottom:1px solid var(--border-soft);padding-bottom:28px}.widget:last-child{border-bottom:none;padding-bottom:0}.widget ul{margin:0;padding:0;list-style:none}.widget-title{font-family:var(--mono);letter-spacing:.1em;text-transform:uppercase;color:var(--text-dim);margin-bottom:16px;font-size:11.5px}.search-form{border:1px solid var(--border);border-radius:8px;display:flex;overflow:hidden}.search-form input{min-width:0;color:var(--text);font-family:var(--sans);background:0 0;border:none;flex:1;padding:10px 12px;font-size:14px}.search-form input:focus{outline:none}.search-form button{background:var(--surface-2);color:var(--text-muted);cursor:pointer;font-family:var(--mono);border:none;padding:0 14px;font-size:12px}.search-form button:hover{color:var(--amber)}.widget-recent ul{flex-direction:column;gap:14px;display:flex}.widget-recent li a{color:var(--text);font-size:14px;line-height:1.4;display:block}.widget-recent li a:hover{color:var(--amber)}.widget-recent li .post-date{font-family:var(--mono);color:var(--text-dim);margin-top:4px;font-size:11px;display:block}.widget-categories li{border-bottom:1px solid var(--border-soft)}.widget-categories li:last-child{border-bottom:none}.widget-categories li.current a{color:var(--amber);font-weight:600}.widget-categories li a{color:var(--text-muted);justify-content:space-between;align-items:center;gap:10px;padding:9px 0;font-size:14px;display:flex}.widget-categories li a:hover{color:var(--text)}.widget-categories .cat-count{min-width:28px;font-family:var(--mono);text-align:center;color:var(--text-dim);background:var(--surface-2);border:1px solid var(--border-soft);border-radius:999px;flex-shrink:0;padding:1px 9px;font-size:11px;display:inline-block}.widget-categories li a:hover .cat-count{color:var(--text)}.widget-more{margin-top:14px}.widget-more a{font-family:var(--mono);color:var(--amber);font-size:12px}.widget-more a:hover{text-decoration:underline}.no-articles{color:var(--text-dim);font-size:13.5px}.widget-connect ul{flex-direction:column;gap:12px;display:flex}.widget-connect li a{color:var(--text-muted);align-items:center;gap:10px;font-size:14px;display:flex}.widget-connect li a:hover{color:var(--text)}.widget-connect li a svg{width:17px;height:17px;color:var(--teal);flex-shrink:0}.widget-approach p{color:var(--text-muted);margin-bottom:12px;font-size:13.5px;line-height:1.7}.widget-approach p:last-child{margin-bottom:0}.breadcrumb-back{font-family:var(--mono);color:var(--text-dim);align-items:center;gap:6px;font-size:13px;display:inline-flex}.breadcrumb-back:hover{color:var(--amber)}.breadcrumb-back svg{width:14px;height:14px}.post-hero{border-bottom:1px solid var(--border-soft);padding:40px 0}.post-hero h1{max-width:820px;margin-top:16px;font-size:38px;line-height:1.2}@media (width<=900px){.post-hero h1{font-size:28px}}.post-meta{font-family:var(--mono);color:var(--text-dim);flex-wrap:wrap;align-items:center;gap:6px 14px;margin-top:22px;font-size:13px;display:flex}.post-meta a{color:var(--text-muted)}.post-meta a:hover{color:var(--amber)}.post-meta .featured-tag{color:var(--brand-red);font-weight:600}.post-body-wrap{padding:52px 0 72px}.post-layout{grid-template-columns:270px 1fr;align-items:start;gap:64px;display:grid}@media (width<=900px){.post-layout{grid-template-columns:1fr;gap:44px}.article{order:1}}.entry{max-width:720px}@media (width<=900px){.entry{max-width:100%}}.entry h2{font-family:var(--mono);color:var(--text);margin-top:48px;margin-bottom:16px;font-size:26px}.entry h2:first-child{margin-top:0}.entry h3{font-family:var(--mono);color:var(--text);margin-top:36px;margin-bottom:14px;font-size:20px}.entry h4{font-family:var(--mono);color:var(--text);margin-top:28px;margin-bottom:12px;font-size:17px}.entry p{color:var(--text-muted);margin-bottom:20px;font-size:16.5px;line-height:1.8}.entry strong{color:var(--text);font-weight:600}.entry a{color:var(--teal);text-decoration:underline;-webkit-text-decoration-color:var(--border);text-decoration-color:var(--border);text-underline-offset:3px}.entry a:hover{color:var(--amber);-webkit-text-decoration-color:var(--amber);text-decoration-color:var(--amber)}.entry ul{margin:0 0 20px;padding-left:0;list-style:none}.entry ul li{color:var(--text-muted);margin-bottom:10px;padding-left:22px;font-size:16px;line-height:1.7;position:relative}.entry ul li:before{content:"";background:var(--amber);border-radius:50%;width:6px;height:6px;position:absolute;top:10px;left:0}.entry figure{margin:28px 0}.entry figure img{border:1px solid var(--border);border-radius:10px;width:100%;height:auto;display:block}.entry .img-row{gap:20px;margin:28px 0;display:flex}@media (width<=900px){.entry .img-row{flex-direction:column}}.entry .img-row figure{flex:1;margin:0}.entry .img-row img{width:100%}.entry code{font-family:var(--mono);background:var(--surface-2);border:1px solid var(--border);border-radius:4px;padding:2px 6px;font-size:.9em}.entry pre{font-family:var(--mono);background:var(--surface-2);border:1px solid var(--border);color:var(--text);border-radius:8px;margin:0 0 20px;padding:16px 18px;font-size:14px;line-height:1.6;overflow-x:auto}.entry pre code{font-size:inherit;background:0 0;border:none;padding:0}.obsolete-warning{border-radius:var(--radius);border:1px solid var(--brand-red);border-left:4px solid var(--brand-red);background:#c71f381f;margin:0 0 40px;padding:20px 24px}.obsolete-warning p{color:var(--text);margin:0;font-size:14.5px;line-height:1.7}.obsolete-warning strong{color:var(--brand-red)}.obsolete-warning a{color:var(--brand-red);text-decoration:underline;-webkit-text-decoration-color:var(--brand-red);text-decoration-color:var(--brand-red);text-underline-offset:3px;font-weight:600}.obsolete-warning a:hover{color:var(--teal);-webkit-text-decoration-color:var(--teal);text-decoration-color:var(--teal)}.callout{background:var(--surface);border:1px solid var(--border);border-left:3px solid var(--brand-red);border-radius:12px;margin:44px 0;padding:28px 30px}.callout h4{font-family:var(--mono);color:var(--text);margin-bottom:12px;font-size:18px}.callout p{color:var(--text-muted);margin-bottom:14px;font-size:14.5px;line-height:1.7}.callout ul{margin:0 0 20px;padding-left:0;list-style:none}.callout ul li{color:var(--text-muted);margin-bottom:8px;padding-left:20px;font-size:14.5px;position:relative}.callout ul li:before{content:"";background:var(--brand-red);border-radius:50%;width:5px;height:5px;position:absolute;top:9px;left:0}.post-nav{grid-template-columns:1fr 1fr;gap:16px;margin:48px 0;display:grid}@media (width<=900px){.post-nav{grid-template-columns:1fr}}.post-nav a{border:1px solid var(--border);background:var(--surface);border-radius:12px;padding:20px 22px;display:block}.post-nav a:hover{border-color:var(--text-dim)}.post-nav .direction{font-family:var(--mono);letter-spacing:.08em;text-transform:uppercase;color:var(--text-dim);margin-bottom:8px;font-size:11px;display:block}.post-nav .title{color:var(--text);font-size:14.5px;line-height:1.4}.post-nav .next-post{text-align:right}.comment-section{border-top:1px solid var(--border-soft);margin-top:56px;padding-top:40px}.comment-section h3{font-family:var(--mono);color:var(--text);margin-bottom:24px;font-size:20px}.comment-form-mock{flex-direction:column;gap:16px;max-width:560px;display:flex}.comment-form-mock label{font-family:var(--mono);text-transform:uppercase;letter-spacing:.06em;color:var(--text-dim);margin-bottom:6px;font-size:12px;display:block}.comment-form-mock input,.comment-form-mock textarea{background:var(--surface);border:1px solid var(--border);width:100%;color:var(--text);font-family:var(--sans);border-radius:8px;padding:11px 14px;font-size:14.5px}.comment-form-mock input:focus,.comment-form-mock textarea:focus{border-color:var(--amber);outline:none}.comment-form-mock textarea{resize:vertical;min-height:120px}.post-hero-v2{padding:20px 0 16px;position:relative}.post-hero-v2:after{content:"";background:linear-gradient(90deg, var(--amber), var(--teal), transparent 85%);border-radius:2px;height:2px;position:absolute;bottom:0;left:0;right:0}.post-hero-v2 h1{max-width:none;margin:12px 0 0;font-size:38px;line-height:1.2}@media (width<=900px){.post-hero-v2 h1{font-size:28px}}.hero-top-row{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:10px 18px;display:flex}.hero-badge{background:var(--teal-dim);border:1px solid var(--teal);color:var(--teal);font-family:var(--mono);letter-spacing:.08em;text-transform:uppercase;border-radius:999px;align-items:center;gap:8px;padding:6px 14px 6px 10px;font-size:12px;display:inline-flex}.hero-badge svg{flex-shrink:0;width:14px;height:14px}.post-meta-v2{font-family:var(--mono);color:var(--text-dim);flex-wrap:wrap;align-items:center;gap:8px 22px;font-size:13px;display:flex}.post-meta-v2 a{color:var(--text-muted)}.post-meta-v2 a:hover{color:var(--amber)}.meta-item{align-items:center;gap:6px;display:inline-flex}.meta-item svg{width:14px;height:14px;color:var(--text-dim);flex-shrink:0}.md-actions{display:inline-flex;position:relative}.md-actions-toggle{background:var(--surface);border:1px solid var(--border);color:var(--text-muted);font-family:var(--sans);cursor:pointer;border-radius:8px;align-items:stretch;padding:0;font-size:13px;transition:box-shadow .15s,border-color .15s,color .15s;display:inline-flex;overflow:hidden}.md-actions-toggle svg{flex-shrink:0;width:14px;height:14px}.md-actions-toggle .chev{width:12px;height:12px;transition:transform .15s}.md-actions-toggle:hover{border-color:var(--text-dim);color:var(--text);box-shadow:0 4px 12px #0000001f}.md-actions-toggle-label{align-items:center;gap:8px;padding:6px 12px;display:inline-flex}.md-actions-toggle-chev{border-left:1px solid var(--border);align-items:center;padding:6px 10px;display:inline-flex}.md-actions.open .md-actions-toggle{border-color:var(--text-dim);color:var(--text);box-shadow:0 4px 12px #0000001f}.md-actions.open .md-actions-toggle .chev{transform:rotate(180deg)}.md-actions-menu{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);z-index:20;flex-direction:column;gap:2px;min-width:240px;padding:6px;display:none;position:absolute;top:calc(100% + 8px);right:0;box-shadow:0 12px 32px #0000002e}.md-actions.open .md-actions-menu{display:flex}.md-actions-item{color:var(--text);font-family:var(--sans);text-align:left;cursor:pointer;background:0 0;border:none;border-radius:8px;align-items:center;gap:10px;padding:8px 10px;font-size:13px;text-decoration:none;display:flex}.md-actions-item svg{width:16px;height:16px;color:var(--text-dim);flex-shrink:0}.md-actions-item:hover{background:var(--surface-2);color:var(--amber)}.md-actions-item:hover svg{color:var(--amber)}.md-actions-bar{justify-content:flex-end;margin-bottom:12px;display:flex}.md-actions-bar+h2{margin-top:0} diff --git a/public/llms-full.txt b/public/llms-full.txt index 4c127a9d..c5449e70 100644 --- a/public/llms-full.txt +++ b/public/llms-full.txt @@ -7494,6 +7494,36 @@ A: The official documentation is linked in the article at docs.dotkernel.com/Pre - [Dotkernel Templates documentation](https://docs.dotkernel.com/Prerequisites/Templates) + + + + +--- +title: "Test article" +description: "This is a test article used for fixture and testing purposes." +author: "admin" +date_published: "2026-08-06" +canonical_url: "https://www.dotkernel.com/dotkernel/test-article/" +category: "Dotkernel" +language: "en" +--- + +# Test article + +## TL;DR + +This is a test article added to the dotkernel category for testing purposes. + +## Overview + +This is a test article used for fixture and testing purposes. + +## FAQ + +**Q: What is this article for?** +A: This is a test article added to the Dotkernel category for fixture and testing purposes. + + @@ -15198,3 +15228,2331 @@ A: Yes, Zend Framework 1.12.13 was released on May 20, 2015, to fix a regression **Q: Where can you read the official announcements for these releases?** A: The 1.12.12 announcement is linked as "Zend Framework 1.12.12 Released", and the 1.12.13 regression fix announcement is linked as "Release Announcement". + + + + + +--- +title: "About us | The team behind Dotkernel" +description: "Dotkernel is built by the dev team at Apidemia - a team that reviews every change together, gives its work back to the PHP community under MIT, and puts guardrails around the tools it adopts." +canonical_url: "https://www.dotkernel.com/about/" +language: "en" +--- + +# The team behind Dotkernel + +About us · Apidemia + +Dotkernel started as an internal tool for handling complex architectures, built because we needed it ourselves. +We released it under MIT because the PHP community gave us the components it stands on. +Everything here is made by a team that has to live with its own decisions. + +- [Talk to us](https://www.dotkernel.com/contact/) +- [View on GitHub](https://github.com/dotkernel) +- [Join the discussion](https://github.com/orgs/dotkernel/discussions) + +| | | +| --- | --- | +| Team | Apidemia | +| License | MIT | +| Released continuously since | 2018 | + +## How a change ships + +Propose (plan before code) → Build (house structure) → Review (another pair of eyes) → QA gate (cs · PHPStan · tests) → Document (for the next person) → Release (MIT, in public). + +## A team that shares the whole codebase + +Nobody here owns a private corner of the code. +Every module follows the same structure, every handler is named the same way, and every application is assembled from the same explicit wiring - so any developer on the team can open any part of the platform and know where they are. +That is a deliberate design choice, and it is what makes reviewing each other's work possible rather than polite. + +Getting better at this is part of the job, not something we hope happens after hours. +Our developers read the upstream source of the components we depend on, write the documentation the next person will need, and turn every repeated mistake into a written rule instead of a reprimand. +When a review catches the same thing twice, that is a gap in what we have written down - and we fix the writing. + +Extending the power of Mezzio by Laminas. + +- Every change reviewed by someone else +- One structure across every application +- Learning time counted as real work +- Decisions written down, not remembered + +## Six things we are not willing to trade + +None of these are aspirations we printed on a wall. +Each one shows up somewhere you can check - in a repository, in a license, in a config file, or in the way a release is handled. + +### Better together than alone — How we work · Teamwork + +The codebase is the team's, not a collection of territories. + +Work is planned out loud before it is written, reviewed before it is merged, and documented before it is called done. +Consistency is what makes that cheap: because a module in one application looks like a module in every other, a reviewer spends their attention on the decision being made rather than on finding their way around. + +### Developers who keep growing — People · Improvement + +Nobody stays where they started. + +Our developers learn by reading the source of the components they build on, by writing the guides and reference docs the rest of the team relies on, and by taking the review notes that come back to them as material rather than criticism. +Every breaking change we publish ships with the article explaining it - writing that explanation is how we make sure we actually understood the change. + +### Giving back to PHP — Community · Open source + +MIT licensed, and answered in public. + +Dotkernel stands on Mezzio, Laminas and Doctrine, so our work goes back out under a permissive license with nothing held behind a paid tier. +The team answers questions on our own components and on the Laminas ones underneath them, publishes the support status of every `dot-*` package instead of leaving it to guesswork, and keeps the discussion where anyone can read it. + +- [Packages lifecycle](https://www.dotkernel.com/dotkernel-packages-oss-lifecycle/) +- [Discussions](https://github.com/orgs/dotkernel/discussions) + +### Every need you can imagine — Customers · Delivery + +The unglamorous requirement is the one that decides a project. + +Our skeletons come out of real client work, which is why they answer questions nobody asks in a demo: sharing a database with an application that was there first, running several connections at once, anonymizing an account instead of deleting the row, hearing about a bug that never threw an exception. +We would rather carry that detail for you than let you discover it in your second sprint. + +### AI with guardrails on — Tooling · Responsible AI + +Adopted everywhere, trusted nowhere by default. + +AI assistance runs through every part of how we work, under rules we wrote down first. +Secrets are never loaded rather than merely redacted, so credentials and signing keys stay out of the context window entirely. +Sessions start by proposing a plan instead of editing files. +Linters report and never rewrite. +A package is never named from memory - it is verified against what is actually installed. +And a human reviews every diff, because accountability does not delegate. + +- [How we do it](https://www.dotkernel.com/dotboost/) + +### Best product, best tools — Craft · Quality + +Chosen for what they will still be worth in three years. + +We build on interfaces from the PHP Framework Interop Group so a component from one vendor can be replaced by another, keep static analysis at a strict level, ship the test suite with the skeleton rather than promising it later, and stay current with the PHP versions our dependencies support. +When a tool stops being the right answer, we say so - publicly, with a migration path. + +- [See the architecture](https://www.dotkernel.com/architecture/) + +## Where you can check any of this + +### The QA gate + +Coding standards, static analysis at a strict rule level and the test suite all run before a change is merged - and "make the checker quiet" is never an accepted fix. + +### Documentation as a deliverable + +A feature is not finished until the next developer can pick it up from what is written, which is why [docs.dotkernel.org](https://docs.dotkernel.org/) is maintained alongside the code. + +### Deprecations, not surprises + +Endpoints announce their own retirement through response headers, so a client finds out from the API rather than from a changelog nobody read. + +### Support status in public + +Every package publishes where it sits in its lifecycle, so choosing one is never a bet on whether it is still maintained. + +### Secrets stay out of the tooling + +Local configuration, environment files and OAuth keys are unreadable to our AI tooling by configuration, not by convention. + +### Supported by JetBrains + +Our open-source tooling is supported through the [JetBrains open source programme](https://jb.gg/OpenSourceSupport) - one of the ways this work stays sustainable. + +[Work with us →](https://www.dotkernel.com/contact/) + +## Open source, in production + +Built by a team that has to live with its own decisions. + +Dotkernel is developed and led by the dev team at Apidemia. +If you are weighing up a platform, hiring a team, or just want to argue about middleware, we would rather have the conversation than the brochure. + +[Talk to us →](https://www.dotkernel.com/contact/) + + + + + + +--- +title: "Dotkernel Admin | Open-source PHP admin application" +description: "Dotkernel Admin is an open-source admin application (skeleton) built on Mezzio, Laminas and Doctrine. Table-based record management, RBAC guards, CSRF-protected forms and 2FA, over the same Core as Dotkernel API." +canonical_url: "https://www.dotkernel.com/admin/" +language: "en" +--- + +# Dotkernel Admin + +Admin application · Server-rendered + +An open-source application skeleton for standing up the administration site behind your platform. +A fast, reliable way to manage the records in your database with a simple table-based approach, and to build the reports and graphs that let you monitor what is happening - with the graphical components for an intuitive experience already in place. + +- [Read the docs](https://docs.dotkernel.org/admin-documentation/) +- [View on GitHub](https://github.com/dotkernel/admin) +- [Live demo](https://admin7.dotkernel.net/) + +| | | +| --- | --- | +| Runtime | Mezzio + Laminas | +| Templating | Twig | +| License | MIT | + +## Request lifecycle + +Session (`dot-session`) → Router (`dot-router`) → Authentication (`laminas-authentication`) → RBAC guard (`dot-rbac-guard`) → Your handler (PSR-15) → Response (Twig template). + +## The back office, not a framework to learn + +Dotkernel Admin is the complementary application of the Dotkernel Headless Platform: an independent app built on the same Mezzio and Laminas foundation as Dotkernel API, sharing the same tech stack so the two form one consistent system rather than two codebases that merely talk to each other. + +Authentication, role-based access control, CSRF-protected forms, validation, navigation and a dashboard are already assembled. +What you add is your own screens for your own entities. + +Extending the power of Mezzio by Laminas. + +- Table-based record management +- Every shipped form validated and CSRF-guarded +- Shares the Core module with API +- Twig templates, npm asset pipeline + +## The unglamorous parts, already done + +Everything below works in a fresh install. +The default modules are configured; your custom functionality needs the same configuration entries, and the docs say exactly which ones. + +### RBAC guards — Security · Access control + +Permissions declared in config, enforced per route handler. + +`dot-rbac` and `dot-rbac-guard` work together: roles and their permissions live in `authorization.global.php`, while `authorization-guards.global.php` maps each route handler to the permissions it requires. +Add a route, add its rule - access control never drifts into your handlers. + +### TOTP two-factor — Security · 2FA + +A password plus a code that expires in 30 seconds. + +`dot-totp` adds time-based one-time passwords following the industry standard: administrators authenticate with their password and a 6-digit code from an authenticator app. +Installation is a documented set of forms, handlers, middleware and three new columns. + +- [Install guide](https://docs.dotkernel.org/admin-documentation/v7/tutorials/install-dot-totp/) + +### CSRF protection — Security · Forms + +A fresh token per render, on every form that ships. + +Built from the `laminas-form` CSRF element, the `laminas-session` CSRF validator and the `formElement` view helper. +Tokens are not reusable between forms and expire after a configurable timeout, defaulting to one hour. + +### Forms & input filters — Input · Validation + +Validation rules that live beside the form, not in the handler. + +`laminas-form` bridges your domain models and the view layer, while `laminas-inputfilter` normalizes and validates the submitted set. +Every form in the skeleton has its inputs filtered, so your own forms have a pattern to copy. + +### Doctrine ORM — Data · Persistence + +Migrations and fixtures, both driven from the CLI. + +Doctrine ORM 3 and DBAL 4 over MariaDB or PostgreSQL, with UUID identifiers via `ramsey/uuid-doctrine`. +Generate a migration with `doctrine-migrations diff`, apply it with `migrate`, then seed the tables with `bin/doctrine fixtures:execute`. + +### Attribute injection — Wiring · DI + +Constructor injection declared where the constructor is. + +`dot-dependency-injection` reads an `#[Inject]` attribute on the constructor and resolves each listed dependency - including values from a configuration key, by dot notation. +Register the class against `AttributedServiceFactory` and you are done writing factories by hand. + +### Menus & UI components — Interface · Navigation + +Configuration-driven menus, ready-made interface pieces. + +`dot-navigation` defines and parses the top menu from `navigation.global.php`. +The `Page` module carries reusable dropdowns, modal popups, error displays and tooltips; the `Dashboard` module holds the landing page layout and its widgets. + +### Commands & lock files — Operations · CLI + +Cron-safe console commands out of the box. + +`dot-cli` builds the console application on top of laminas-cli, writing lock files into `data/lock` so a scheduled command cannot overlap with itself. +GeoLite2 databases sync through `bin/cli.php geoip:synchronize`, quiet mode included for cron jobs. + +### Mail and error handling — Operations · Mail & logs + +Transactional mail configured in one file, errors in another. + +`dot-mail` handles sendmail or SMTP delivery, sender identity and CC lists from `mail.global.php`. +`dot-errorhandler` writes daily log files to `log/`, with the format set in `error-handling.global.php`. + +## Modules you can name before you read them + +Admin follows PSR-4, with one folder per module. +Each module keeps its handlers, input filters and services together, plus a `ConfigProvider` and a `RoutesDelegator` - so the second module you write looks like the first. + +### Shared domain layer + +`Core\Admin` `Core\App` `Core\Security` `Core\Setting` `Core\User` + +The Core module is a common codebase shared with the other applications in your project. +Each submodule holds its entities, repositories and its own ConfigProvider, so a user means the same thing to the admin screen and to the API endpoint. + +### `Admin` + +Managing the users that hold the `admin` role - the accounts stored in the `admin` database table. + +### `User` + +Managing the platform's own users, stored separately in the `user` table. +Two audiences, two models, no overloaded role column. + +### `App` + +Core application functionality: authentication, rendering and error reporting. + +### `Dashboard` + +The default landing page - its layout, widgets and rendering logic. + +### `Page` + +Reusable interface elements: dropdowns, modals, error displays, tooltips. + +### `Setting` + +Saving and reading display settings for the administration interface. + +## From clone to login page + +The full walkthrough, with expected output for every command, is in the documentation. +This is the shape of it. + +### 1 · Clone the project + +Into an empty directory of your choosing. + +```shell +git clone https://github.com/dotkernel/admin.git . +``` + +### 2 · Install dependencies + +Run it from the CLI so the setup prompts stay interactive. +Decline the ConfigProvider injection - Dotkernel ships its own. + +```shell +composer install +``` + +### 3 · Enable development mode + +For local work only. +`composer development-status` tells you where you stand, and it must stay off in production. + +```shell +composer development-enable +``` + +### 4 · Configure the database + +Fill in the credentials in `config/autoload/local.php`. +Both a MariaDB and a PostgreSQL connection are pre-declared; one is active at a time. + +### 5 · Migrate and seed + +Migrations build the schema; fixtures populate the default roles, OAuth clients and accounts. + +```shell +php ./vendor/bin/doctrine-migrations migrate +php ./bin/doctrine fixtures:execute +``` + +### 6 · Build the assets + +`npm run watch` recompiles while you work; `npm run prod` minifies for release. + +```shell +npm install && npm run prod +``` + +Open the virtual host in your browser and the Dotkernel Admin login page is waiting. +If the fixtures ran, sign in with user `admin` and password `dotadmin` - the same credentials as the public demo, and the first thing to change before going live. + +## What the server needs + +| Component | Requirement | +| --- | --- | +| Operating system | A \*nix based system is strongly recommended for production. | +| PHP | 8.2 or newer, mod_php or FCGI (FPM). `memory_limit` at least 128M; `upload_max_filesize` and `post_max_size` at least 100M depending on your data. | +| Web server | Apache 2.2+ with `mod_rewrite` and `.htaccess` support (`AllowOverride All`) - a default `.htaccess` ships in `public/`. On Nginx, translate it into server configuration. | +| Database | MariaDB 10.7, 10.11 LTS, 11.4 LTS and 11.8 LTS, or PostgreSQL 13 and above. **MySQL is not supported**, as it has no UUID support. | +| Required extensions | `mbstring`, the CLI SAPI for cron jobs, and Composer available on `$PATH`. | +| Recommended extensions | `opcache`; `pdo_mysql`, `pdo_pgsql` or `mysqli` to match your database; `dom` and `simplexml` for markup; `gd` and `exif` for images; `zlib`, `zip`, `bz2` for compression; `curl` when calling APIs; `sqlite3` for the test suite. | + +On Windows, WSL2 is the recommended development environment - Dotkernel provides an AlmaLinux distro implementation and an install script for the whole local stack. + +## The checklist nobody writes down + +Admin gives you the tools to build a safe application, and the documentation is explicit that some of them are yours to apply. +These are the ones worth checking off before the first deploy. + +- [Security guide](https://docs.dotkernel.org/admin-documentation/v7/security/basic-security/) + +### Before you go to production + +- Change or remove the demo admin account - its identity and password are public. +- Review `cookie_httponly`, `cookie_samesite` and `cookie_secure` in `session.global.php`. +- Confirm development mode is off with `composer development-status`. +- Set your allowed origins in `cors.global.php`. +- Keep secrets in `*.local.php` - the `*.global.php` and `*.php.dist` files are committed. +- Add CSRF fields and authorization rules to every form and route you add. +- Watch dependencies on both sides: Composer advisories and `npm audit`. + +## Upgrades you can read before you run them + +Admin does not ship an automatic upgrade path, and that is deliberate: you implement the modifications listed for each release, in a codebase you already understand, instead of handing a migration script control over your customizations. + +A `CHANGELOG.md` in the project root lists implemented features in reverse chronological order, so it doubles as your version marker. +From version 6.2 onward, the procedure is documented version to version, and releases are also published as an RSS feed. + +- [Releases](https://github.com/dotkernel/admin/releases) +- [Upgrade guide](https://docs.dotkernel.org/admin-documentation/v7/upgrading/upgrading/) + +### Coming from Admin 6.x? + +Version 7 bumped dependencies, synchronised the Core module and refreshed the codebase to stay compatible with Dotkernel API v7 - the release that moved identifiers to native database UUIDs and dropped MySQL. + +Work through the pull requests listed for 7.0, then copy the release notes into your own changelog so your project keeps tracking upstream. + +## Better with an API in front of it + +Admin installs and runs on its own. +Paired with Dotkernel API over the same `Core` namespaces, Admin manages the data while the API exposes it to third-party frontends and backends - and neither one disagrees about what an entity is. +The rest of the ecosystem is below. + +### API — Pair with · HTTP surface + +Expose the data you manage here to any client. + +A REST API on a PSR-15 middleware pipeline, with OAuth 2.0, RBAC, HAL payloads and an OpenAPI 3.0 specification wired up on install. + +- [Read more](https://www.dotkernel.com/api/) +- [GitHub](https://github.com/dotkernel/api) +- [Demo](https://api.dotkernel.net/) + +### Queue — Pair with · Async work + +Queue a bulk operation from an admin screen. + +Background workers on Symfony Messenger - a TCP listener, Valkey streams, retries and a dead letter queue for what still fails. + +- [Read more](https://www.dotkernel.com/queue/) +- [GitHub](https://github.com/dotkernel/queue) + +### Frontend — Instead · Public-facing + +The site your users log into, not your staff. + +A web starter skeleton - user accounts, a contact form, sessions and RBAC-guarded controller actions, rendered on the server. + +- [Read more](https://www.dotkernel.com/frontend/) +- [Demo](https://v5.dotkernel.net/) + +### Light — Smaller · Minimal + +No records to manage, just pages. + +The smallest complete Mezzio application - routing, pipeline and Twig, six direct dependencies and no database layer. + +- [Read more](https://www.dotkernel.com/light/) +- [Demo](https://light.dotkernel.net/) + +### Dotboost — Tooling · AI context + +Teach your AI tools this architecture. + +Drop-in Claude Code configuration - ten commands, seventeen skills and permission guardrails that keep your secrets out of the context window. + +- [Read more](https://www.dotkernel.com/dotboost/) +- [GitHub](https://github.com/dotkernel/dotboost) + +New modules start with [dot-maker](https://docs.dotkernel.org/dot-maker/v1/overview/), which knows which files an Admin needs versus an API. +Support status for every layer is on the [packages lifecycle](https://www.dotkernel.com/dotkernel-packages-oss-lifecycle/) page. + +## Open source, in production + +Try it with the demo, keep it under MIT. + +Sign in to the public demo with `admin` / `dotadmin` to see the interface before you install anything. +Dotkernel Admin is developed and led by the dev team at Apidemia, and released as open source for the community. + +[Talk to us →](https://www.dotkernel.com/contact/) + + + + + + +--- +title: "Dotkernel API | Open-source REST API skeleton for PHP" +description: "Dotkernel API is an open-source REST API skeleton built on Mezzio, Laminas and Doctrine. OAuth2, RBAC, HAL, problem details and OpenAPI 3.0 come wired together on install. MIT licensed." +canonical_url: "https://www.dotkernel.com/api/" +language: "en" +--- + +# Dotkernel API + +REST API skeleton · PSR-15 middleware + +An open-source REST API skeleton for PHP, built on the Mezzio microframework and Laminas components over a Doctrine domain layer. +OAuth 2.0, RBAC authorization, HAL payloads, standardized error responses and an OpenAPI 3.0 specification are assembled on install - not left as decisions for your first sprint. + +- [Read the docs](https://docs.dotkernel.org/api-documentation/) +- [View on GitHub](https://github.com/dotkernel/api) +- [Live demo](https://api.dotkernel.net/) + +| | | +| --- | --- | +| Runtime | Mezzio + Laminas | +| Persistence | Doctrine ORM | +| License | MIT | + +## Request lifecycle + +CORS (`mezzio-cors`) → Router (FastRoute) → Authentication (OAuth 2.0) → RBAC guard (per route name) → Your handler (PSR-15) → Response (HAL / problem+json). +[All 19 stages](https://www.dotkernel.com/architecture/) + +## A REST API you own from the first commit + +Dotkernel API is the root of the Dotkernel Headless Platform: a REST API based on the Mezzio skeleton, using a PSR-compliant middleware stack as defined by the PHP Framework Interop Group. +Rather than relying on elements built into a framework, business logic is built explicitly - from handlers to dependencies - so you keep full control over your business logic and data architecture. + +It scales down to a single microservice and up to an enterprise-grade API, and it has been developed and released continuously since 2018. + +Extending the power of Mezzio by Laminas. + +- Doctrine ORM, not Active Record +- Explicit wiring, no runtime magic +- Auth, docs and errors on install +- MIT licensed, actively maintained + +## The parts every API needs, already connected + +Each of these is configured and working in a fresh install. +Nothing here is a placeholder you have to research and assemble before you can serve your first authenticated endpoint. + +### OAuth 2.0 — Security · Authentication + +Bearer tokens, issued and validated by the API itself. + +Authentication runs on `mezzio-authentication-oauth2`, which wraps `league/oauth2-server` to provide OAuth 2.0 for PSR-7 / PSR-15 applications. +Endpoints marked as authenticated require a valid Bearer token and answer `401 Unauthorized` without one. + +### RBAC per route — Security · Authorization + +Three levels of protection: none, authenticated, authorized. + +Roles and their permissions are declared in configuration and allocated per route name, with role inheritance supported. +An authenticated request that lacks the required permission gets a `403 Forbidden` - decided by config, not scattered through your handlers. + +### OpenAPI 3.0 — Contract · Documentation + +A machine-readable contract, generated from the code that serves it. + +Every module documents its endpoints in an `OpenAPI.php` file, which `zircote/swagger-php` turns into a specification rendered through Swagger UI or Redoc. +A Postman collection and environment ship alongside it, so every endpoint is ready to call by hand. + +### Doctrine ORM — Data · Persistence + +Your domain in entities, not in the query layer. + +Doctrine ORM handles persistence, so you can focus on object-oriented business logic and treat storage as a secondary concern. +Migrations, fixtures via `dot-data-fixtures`, and multiple database connections are part of the setup. + +### HAL & problem details — Contract · Responses + +One predictable shape for resources, one for failures. + +Payloads are built with `mezzio-hal`, describing each resource together with its relational links and embedded child resources. +Errors return problem details responses via `mezzio-problem-details`, giving clients standardized error codes system-wide. + +### Content negotiation — Contract · Negotiation + +Client and server agree on format and language up front. + +Content negotiation is implemented out of the box through the `Content-Type` and `Accept` headers, so diverse systems can consume the same API without custom glue on either side. +CORS preflight requests are recognized and configured by `mezzio-cors`. + +### Error reporting endpoint — Operations · Feedback + +A channel for the bugs that never reach your logs. + +Frontend developers can report incorrect behaviour by posting to `/error-report` with a token header. +The API validates the request against configured tokens, domains and IPs before logging it - useful precisely when nothing fatal was thrown. + +### Commands & file locker — Operations · CLI + +Scheduled work that refuses to trample itself. + +Console commands are registered through `dot-cli` on top of Symfony Console, and `php ./bin/cli.php route:list` prints every endpoint the application exposes. +The file locker, enabled by default, writes a lock file per command so a second instance cannot start while the previous run is still going - the safeguard cron jobs usually lack. + +### Transactional email — Operations · Mail + +Account mail that works before you configure anything. + +`dot-mail` wraps Symfony Mailer with templated messages, so account activation and password reset send out of a fresh install. +It joins the request flow only where it is needed - the rest of the pipeline is unchanged whether a route sends mail or not. + +### dot-maker — Productivity · Scaffolding + +New modules that already match the house style. + +`dot-maker` generates project files and directories following the Dotkernel structure, and knows which files each application type needs. +It replaces hand-copied boilerplate with consistent, standardized scaffolding. + +- [Docs](https://docs.dotkernel.org/dot-maker/v1/overview/) + +## What version 7 changed + +The latest release moves identifiers to native database UUIDs and broadens platform support. + +### Native UUID v7 + +Identifier columns use the database's `uuid` type instead of `binary`, with values generated by `ramsey/uuid`. +You keep full control of the UUID version without depending on database extensions. + +### PostgreSQL support + +PostgreSQL joins the supported databases. +Because native UUID is required, you need PostgreSQL or MariaDB 10.7 or later; MySQL is no longer supported, as it has no UUID data type. + +### PHP 8.5 + +The API targets PHP 8.5, with Dotkernel Admin on 8.4. +Dependencies are kept current, and the ecosystem's own packages track the versions Laminas and Doctrine support. + +### Table prefixes + +A configurable string can be prepended to every table name - the practical requirement when an API shares a database with an existing application. + +### Clearer database configuration + +Multi-connection setups spell out which connection is the default and how to switch to another, based on scenarios from real projects. + +### Shared Core module + +Common logic lives in a Core module kept as its own Git repository, which can be added as a submodule to any Dotkernel application so entities and queries stay consistent. + +## Evolution, not a wall of versions + +Dotkernel API favours an evolution pattern with a sunsetting mechanism over maintaining parallel API versions. +The same codebase evolves gradually and clients are given notice, instead of every change forking into another branch you have to keep alive. + +The mechanism is `DeprecationMiddleware`: mark a handler with the `#[ResourceDeprecation]` attribute and the response carries `sunset` and `link` headers, so a client learns an endpoint is retiring from the endpoint itself rather than from a changelog it never read. + +Full versioning stays reserved for major, format-level changes - the cases where it genuinely earns its maintenance cost. +The two approaches are not mutually exclusive. + +- [API evolution guide](https://docs.dotkernel.org/api-documentation/v6/tutorials/api-evolution/) +- [Where it sits in the pipeline](https://www.dotkernel.com/architecture/) + +### Coming from Laminas API Tools? + +API Tools (formerly Apigility) is archived. +Dotkernel API is an actively maintained alternative with a middleware architecture, a permissive MIT license and a Doctrine data layer. + +The comparison below is drawn from the full side-by-side write-up on our blog. + +## Dotkernel API vs Laminas API Tools + +| Feature | API Tools (formerly Apigility) | Dotkernel API | +| --- | --- | --- | +| First release | 2012 | 2018 | +| Architecture | MVC, event driven | Middleware | +| OSS lifecycle | Archived | Active | +| PHP version | ≤ 8.2 | See the PHP version badge for `dotkernel/api` | +| Style | REST, RPC | REST | +| Change management | Versioning | Deprecations (API evolution) | +| Documentation | Swagger (automated) | OpenAPI 3.0 (Swagger) and Postman (manual) | +| License | BSD-3 | MIT | +| Default DB layer | laminas-db | doctrine-orm 3.x | +| Authorization | ACL | RBAC guard | +| Authentication | HTTP Basic / Digest, OAuth 2.0 | OAuth 2.0 | +| Endpoint generator | Yes | dot-maker | +| PSR standards | PSR-7 | PSR-7, PSR-15 | + +Comparison drawn against Dotkernel API v7. +Read the reasoning behind each row in [Dotkernel API versus Laminas API Tools](https://www.dotkernel.com/dotkernel-api/dotkernel-api-versus-laminas-api-tools/). + +## Runs alone, or as one of three + +Dotkernel API is a complete application on its own. +When you add Admin or Queue, all three declare the same `Core` namespaces, so a `User` means the same thing to the endpoint that creates it, the admin screen that moderates it, and the worker that emails it. +The rest of the ecosystem is below. + +### Shared domain layer + +`Core\App` `Core\Admin` `Core\User` `Core\Security` `Core\Setting` + +One set of entities and repositories, committed in each repository so a single-component start just works. +Having many entities in Core does not oblige you to implement handlers for all of them - each application handles only what it needs. + +### Admin — Pair with · Back office + +Manage the same data your API serves. + +Table-based record management with RBAC guards, CSRF-protected forms and 2FA, over the same Core module. +Start with either one and add the other later. + +- [Read more](https://www.dotkernel.com/admin/) +- [GitHub](https://github.com/dotkernel/admin) +- [Demo](https://admin7.dotkernel.net/) + +### Queue — Pair with · Async work + +Move slow work off the request cycle. + +Background workers on Symfony Messenger - a TCP listener, Valkey streams, retries and a dead letter queue for what still fails. + +- [Read more](https://www.dotkernel.com/queue/) +- [GitHub](https://github.com/dotkernel/queue) + +### Frontend — Instead · Server-rendered + +When your users want pages, not payloads. + +A web starter skeleton - user accounts, a contact form, sessions and RBAC-guarded controller actions, rendered on the server. + +- [Read more](https://www.dotkernel.com/frontend/) +- [Demo](https://v5.dotkernel.net/) + +### Light — Smaller · Minimal + +A presentation site with no API behind it. + +The smallest complete Mezzio application - routing, pipeline and Twig, six direct dependencies and no database layer. + +- [Read more](https://www.dotkernel.com/light/) +- [Demo](https://light.dotkernel.net/) + +### Dotboost — Tooling · AI context + +Teach your AI tools this architecture. + +Drop-in Claude Code configuration - ten commands, seventeen skills and permission guardrails that keep your secrets out of the context window. + +- [Read more](https://www.dotkernel.com/dotboost/) +- [GitHub](https://github.com/dotkernel/dotboost) + +Every layer is also available on its own - see the [packages lifecycle](https://www.dotkernel.com/dotkernel-packages-oss-lifecycle/) for the support status of each `dot-*` package. + +## Built on interfaces, checked on every change + +### [PSR-15 & PSR-7](https://www.php-fig.org/psr/psr-15/) + +Handlers implement `RequestHandlerInterface` and return `ResponseInterface`. +The whole request path is middleware you can read top to bottom. + +### [PSR-11 & PSR-4](https://www.php-fig.org/psr/psr-11/) + +The application is container-based, with dependencies declared in each module's `ConfigProvider`, and classes located by autoloader. + +### [PSR-3 logging](https://www.php-fig.org/psr/psr-3/) + +Errors are logged through `LoggerInterface` via `dot-errorhandler`, centralizing how failures are recorded. + +### [PHPStan at level 8](https://phpstan.org/) + +Static analysis runs at a strict rule level, in line with the choice made by projects like Doctrine and Composer. + +### Functional & unit tests + +The skeleton ships with a test suite you extend rather than start, so new endpoints have somewhere to be tested from day one. + +### An active community + +Updates arrive with bugfixes and improvements from the PHP community, and breaking changes come with companion articles and upgrade steps. + +[Join the discussion →](https://github.com/orgs/dotkernel/discussions) + +## Install it and call an endpoint + +Create the project with Composer, point it at PostgreSQL or MariaDB 10.7+, run the migrations, and you have an authenticated REST API with a browsable OpenAPI specification. + +- [Installation guide](https://docs.dotkernel.org/api-documentation/) +- [Try the demo](https://api.dotkernel.net/) + +### composer create-project dotkernel/api + +The installer walks through database credentials and the initial configuration. +Follow the installation guide for the exact command and post-install steps for your target version, then use `dot-maker` to scaffold your first module. + +Questions along the way go to [Dotkernel Discussions](https://github.com/orgs/dotkernel/discussions) - the team answers issues on both Dotkernel and Laminas components. + +## Open source, in production + +An API foundation you can read, audit and keep. + +Dotkernel API is developed and led by the dev team at Apidemia - built first as an internal tool for handling complex architectures, released under MIT as our way of giving back to the community. + +[Talk to us →](https://www.dotkernel.com/contact/) + + + + + + +--- +title: "Architecture | How a request moves through a Dotkernel application" +description: "The PSR-15 middleware pipeline behind Dotkernel API, stage by stage: error boundary, routing, content negotiation, deprecation headers, OAuth 2.0, RBAC and dispatch - plus the library flow underneath it." +canonical_url: "https://www.dotkernel.com/architecture/" +language: "en" +--- + +# How a request moves through a Dotkernel application + +Architecture · PSR-15 pipeline + +Every Dotkernel application is a stack of PSR-15 middleware with no framework runtime hidden underneath it. +A request enters at the error boundary, passes through routing, negotiation, authentication and authorization, and reaches your handler - and you can read every layer it crossed on the way. + +- [Read the docs](https://docs.dotkernel.org/api-documentation/) +- [See pipeline.php](https://github.com/dotkernel/api/blob/7.0/config/pipeline.php) +- [Dotkernel API](https://www.dotkernel.com/api/) + +| | | +| --- | --- | +| Contract | PSR-7 + PSR-15 | +| Container | PSR-11 | +| Logging | PSR-3 | + +## Five phases, 19 stages + +Error boundary (1 stage) → Request preparation (4 stages) → Routing (4 stages) → Contract & headers (4 stages) → Identity (2 stages) → Dispatch & fallback (4 stages). + +## Framework-less, not framework-free + +Dotkernel is an opinionated, framework-less tool aimed at intermediate-to-advanced programmers. +Framework-less does not mean nothing is underneath: Mezzio and Laminas components are there, and they are excellent. +It means there is no framework runtime deciding things on your behalf. +The pipeline is a list you wrote, in a file you can open, executed top to bottom. + +That is the whole trade. +You give up the convenience of conventions you never have to look at, and you get an application where every layer between the socket and your handler is named, ordered and yours to reorder. + +Extending the power of Mezzio by Laminas. + +- Gradual learning curve +- Fine-tuned security +- KISS, YAGNI, DRY +- Long-term support + +## Nineteen stages, in the order they run + +This is the default pipeline of Dotkernel API, unedited. +Each stage receives the request, may hand it onward, and may alter the response on the way back out - which is why the error boundary is first in and last out. + +Legend: Mezzio / Laminas component · Dotkernel. + +### Error boundary + +#### 01 · ProblemDetailsMiddleware — mezzio/mezzio-problem-details + +The outermost layer, deliberately. +Anything thrown deeper in the stack is handed to `dot-errorhandler` for logging and comes back as a problem details response, so a failure reaches the client as a documented error shape rather than a stack trace. + +### Request preparation + +#### 02 · MalformedRequestBodyMiddleware — dotkernel/api + +Rejects a body that is not valid JSON up front, before any later stage tries to read it and fails in a less legible place. + +#### 03 · BodyParamsMiddleware — mezzio/mezzio-helpers + +Parses the raw request body into the parsed body according to its `Content-Type`. + +#### 04 · ServerUrlMiddleware — mezzio/mezzio-helpers + +Seeds the server URL helper with the current scheme and host, so anything generating a URL later produces an absolute one. + +#### 05 · CorsMiddleware — mezzio/mezzio-cors + +Recognises preflight requests and answers them, and attaches the configured CORS headers to everything else. +It sits before routing so a preflight never needs a matched route. + +### Routing + +#### 06 · RouteMiddleware + +*mezzio/mezzio-router* — Route + +Matches the request against the route table and registers the `RouteResult` attribute. +Everything below this line can ask which route was matched - and most of it does. + +#### 07 · ImplicitHeadMiddleware — mezzio/mezzio-router + +Answers `HEAD` for routes that only declare `GET`. + +#### 08 · ImplicitOptionsMiddleware — mezzio/mezzio-router + +Answers `OPTIONS` with the methods the matched path actually allows. + +#### 09 · MethodNotAllowedMiddleware — mezzio/mezzio-router + +Returns `405 Method Not Allowed` when the path matched but the method did not. +Order matters here: it has to sit after both implicit handlers, or it would answer the requests they exist to serve. + +### Contract & headers + +#### 10 · ContentNegotiationMiddleware — dotkernel/api + +Reconciles the client's `Accept` and `Content-Type` with what the matched route is configured to serve, so diverse consumers share one API without custom glue. + +#### 11 · DeprecationMiddleware — dotkernel/api + +The mechanism behind API evolution. +It reads the `#[ResourceDeprecation]` attribute off the handler and, when one is present, sets the `sunset` and `link` response headers - the client learns an endpoint is going away from the endpoint itself, rather than from a migration guide. + +#### 12 · ResponseHeaderMiddleware — dotkernel/dot-response-header + +Applies the headers you configure application-wide to every outgoing response, in one place instead of per handler. + +#### 13 · UrlHelperMiddleware — mezzio/mezzio-helpers + +Seeds the URL helper with the routing result, so handlers and HAL links can be generated relative to the route that was actually matched. + +### Identity + +#### 14 · AuthenticationMiddleware + +*dotkernel/api* — 401 + +Validates the OAuth 2.0 Bearer token. +Routes declared as authenticated answer `401 Unauthorized` without a valid one, and never reach the handler. + +#### 15 · AuthorizationMiddleware + +*dotkernel/api* — 403 + +Checks the authenticated identity's role against the permissions allocated to the matched route name, and returns `403 Forbidden` when it falls short. +The decision lives in configuration, not scattered through handlers. + +### Dispatch & fallback + +#### 16 · ResourceProviderMiddleware — dotkernel/api + +Reads the `#[Resource]` attribute on the handler's `handle()` method and loads the named entity through Doctrine using the route placeholder. +The handler receives a resource rather than an identifier, and a record that does not exist becomes a `404` before your code runs. + +#### 17 · DispatchMiddleware — mezzio/mezzio-router + +Hands the request to the matched PSR-15 handler. +This is your code - everything above was getting the request into a state where your handler only has to do its own job. + +#### 18 · ProblemDetailsNotFoundHandler — mezzio/mezzio-problem-details + +Reached only when nothing above returned a response: emits `404` in problem details form. + +#### 19 · GetNotFoundResourceHandler — dotkernel/api + +The last stage in the pipeline, shaping the not-found response for API clients. + +## The packages underneath, in the same order + +The same journey seen one level down - which library owns each step. +The second column shows how little changes when a feature is added: sending an email inserts one package and leaves the rest of the flow alone. + +### Default flow + +A request that reads or writes data and returns a resource. + +1. Request +2. mezzio/mezzio-problem-details +3. dotkernel/dot-errorhandler +4. mezzio/mezzio-cors +5. mezzio/mezzio-router +6. dotkernel/dot-response-header +7. mezzio/mezzio-helpers +8. mezzio/mezzio-authentication +9. mezzio/mezzio-authorization +10. mezzio/mezzio-router *dispatch* +11. laminas/laminas-diactoros *not found* +12. dotkernel/dot-dependency-injection +13. doctrine/orm +14. mezzio/mezzio-hal +15. laminas/laminas-diactoros +16. psr/http-message +17. Response + +### Flow for sending an email + +Identical until the domain layer, where `dot-mail` joins in. + +1. Request +2. mezzio/mezzio-problem-details +3. dotkernel/dot-errorhandler +4. mezzio/mezzio-cors +5. mezzio/mezzio-router +6. dotkernel/dot-response-header +7. mezzio/mezzio-helpers +8. mezzio/mezzio-authentication +9. mezzio/mezzio-authorization +10. mezzio/mezzio-router *dispatch* +11. laminas/laminas-diactoros *not found* +12. dotkernel/dot-dependency-injection +13. doctrine/orm +14. dotkernel/dot-mail *added* +15. mezzio/mezzio-hal +16. laminas/laminas-diactoros +17. psr/http-message +18. Response + +## Built on interfaces from the Framework Interop Group + +Dotkernel applications are collections of PSR-7 middleware. +The standards below are not badges - they are the reason a package from one vendor drops into a pipeline built by another. + +### [PSR-7](https://www.php-fig.org/psr/psr-7/) + +HTTP messages are immutable value objects. +Every stage above receives a request and returns a response, and neither is a global you have to guard. + +### [PSR-15](https://www.php-fig.org/psr/psr-15/) + +Middleware and request handlers share one interface, which is what makes the pipeline a plain ordered list rather than a framework-specific event graph. + +### [PSR-11](https://www.php-fig.org/psr/psr-11/) + +The application is container-based, with each module declaring its dependencies in a `ConfigProvider`. + +### [PSR-4](https://www.php-fig.org/psr/psr-4/) + +Classes are located by autoloader from their namespace, so where a file lives is never a question of configuration. + +### [PSR-3](https://www.php-fig.org/psr/psr-3/) + +Errors are logged through `LoggerInterface` via `dot-errorhandler`, reached from the error boundary at stage one. + +### Why it matters + +You can delete any stage in the pipeline, or write your own and pipe it in, without asking a framework's permission. +That is the practical payoff of standardising on interfaces. + +[All PSR standards →](https://www.php-fig.org/psr/) + +## One pipeline shape, three applications + +The stages differ where the job differs - Admin adds sessions and CSRF-protected forms, Queue swaps HTTP dispatch for a message consumer - but the pattern is constant, and all three declare the same `Core` namespaces underneath. + +### Shared domain layer + +`Core\App` `Core\Admin` `Core\User` `Core\Security` `Core\Setting` + +Kept as its own Git repository and added as a submodule, so an entity means the same thing to the endpoint that creates it, the admin screen that moderates it and the worker that emails it. + +### [API](https://www.dotkernel.com/api/) + +The pipeline documented above, in full. + +### [Admin](https://www.dotkernel.com/admin/) + +Same shape, plus sessions, CSRF-protected forms and 2FA in the identity phase. + +### [Queue](https://www.dotkernel.com/queue/) + +The same middleware thinking applied to messages instead of requests. + +## The pipeline is one file + +Everything on this page comes from `config/pipeline.php` - about ninety lines, most of them comments explaining where to add your own middleware. +There is no generated layer between that file and what runs. + +- [Installation guide](https://docs.dotkernel.org/api-documentation/) +- [Try the demo](https://api.dotkernel.net/) + +### php ./bin/cli.php route:list + +Once the pipeline has routed a request, the route table is the other half of the picture. +This command lists every endpoint the application exposes, which is the fastest way to review an API you have just inherited. + +Database migrations are generated and run the same way, through the Doctrine console commands registered by `dot-cli`. + +## Open source, in production + +No layer you are not allowed to read. + +Dotkernel is developed and led by the dev team at Apidemia - built first as an internal tool for handling complex architectures, released under MIT as our way of giving back to the community. + +[Talk to us →](https://www.dotkernel.com/contact/) + + + + + + +--- +title: "dotboost | Claude Code configuration for Dotkernel projects" +description: "dotboost is a drop-in .claude/ directory that teaches Claude Code the conventions of any Dotkernel application - ten commands, seventeen skills, six hooks, and permission guardrails that keep secrets out of the context." +canonical_url: "https://www.dotkernel.com/dotboost/" +language: "en" +--- + +# Dotboost + +Developer tooling · AI context + +Drop-in configuration that teaches Claude Code the conventions of any Dotkernel application - API, Admin, Frontend, Light, Queue, or a project derived from one of them. +Every skill detects the variant first and applies the matching dialect, so generated code follows your architecture instead of guessing at it. + +- [View on GitHub](https://github.com/dotkernel/dotboost) +- [Claude Code docs](https://docs.claude.com/en/docs/claude-code/overview) + +| | | +| --- | --- | +| Payload | One .claude/ directory | +| Commands | Ten /dk-* | +| Skills | Seventeen | + +## What happens in a session + +Session start (variant briefing) → Skill loads (by description) → Plan mode (proposes first) → Guard hooks (Edit · Bash) → Normalise (BOM · CRLF · EOF) → Lint (`php -l` · markdown). + +## Not an application - a configuration payload + +Dotboost is not itself a Dotkernel application. +Its entire payload is the `.claude/` directory: settings, hooks, a status line, a review subagent, ten `/dk-*` commands and seventeen skills. +You install it by copying that directory into the Dotkernel project you are working on. + +There is no routing table telling Claude which skill to pick - each one loads from its own description. +Ask where a new Doctrine entity goes and it should reach for the module-structure skill rather than answering from general framework knowledge. + +Maintained by **Borsan Sergiu**. + +- Detects the variant before it answers +- Secrets never enter the context window +- Guards catch compound commands globs miss +- Copy it in - nothing to build or install + +## The reason to adopt the file + +The permission rules in `settings.json` fall into four tiers. +The first one is the headline: these are `Read` denies, not write protection, so the contents never enter the context window at all. + +### Your secrets — Tier · Never read + +Not redacted - never loaded. + +`.env`, every `*.local.php`, `config/autoload/local.php`, `local.test.php` and `data/oauth/`. +Database credentials, OAuth signing keys and environment secrets stay out of the transcript. + +### Generated & vendored — Tier · Never written + +The files a diff should never touch by accident. + +Dependency manifests - `composer.json`, `composer.lock`, `package.json`, `package-lock.json` - plus `vendor/`, `node_modules/`, `data/`, `log/`, `public/uploads/` and anything under `Migration/`. + +### Installs & destructive git — Tier · Never run + +Commands that change your tree without a diff. + +`composer require`, `remove`, `update`, `install`, `global`; the npm, yarn and pnpm install verbs; `git push`, `reset --hard`, `clean`, `submodule`; and `rm -rf`. + +### Your call — Tier · Ask & allow + +Preference, not policy - override per machine. + +**Ask**: migrations, `bin/doctrine`, database clients, git commits and merges, pipeline and authorization config, the QA config files. +**Allow**: the Composer QA scripts, `vendor/bin` tools, `php -l`, and read-only git. +Layer changes in `settings.local.json`, never the shared file. + +### Where globs cannot see — Hooks · Six + +Path patterns miss what a shell can hide. + +`guard-bash.sh` inspects the command itself, so `cd src && composer require foo` is caught where a path-based rule would let it through. +`guard-protected-paths.sh` covers edits, and redirects a blocked `*.local.php` edit to its `.dist` template. + +### Linters that do not rewrite — Hooks · Report only + +Deliberately no `phpcbf`, no `--fix`. + +`php-lint.sh` and `markdown-lint.sh` report and stop, because reformatting a file right after it is written invalidates the in-memory copy and breaks the next targeted edit. +Bulk formatting belongs at the end of a task. +Both skip silently when their tool is missing. + +`settings.json` is a committed file carrying opinions, not only guardrails: sessions start in **plan mode**, the terminal UI is full screen, the theme is dark, and Composer runs without a memory ceiling. +Know what you are adopting. + +## Where the hooks overrule the tiers + +Some commands marked *ask* in `settings.json` are refused outright by `guard-bash.sh`, because the hook sees compound commands the permission globs cannot. +The prompt you would expect never appears - you get a refusal. +This is deliberate, and worth knowing before it surprises you. + +| Command | settings.json says | guard-bash.sh does | +| --- | --- | --- | +| git rebase | ask | blocks | +| doctrine-migrations migrate / execute | ask | blocks | +| fixtures:execute, schema:drop, schema:update | ask, via bin/cli.php | blocks | +| composer development-enable / -disable | — | blocks | +| pip install, git checkout --, git filter-branch | — | blocks | + +Everything else under *ask* - commits, database clients, the config and QA files - prompts as documented. +To get a blocked command back you edit the hook; relaxing `settings.local.json` will not reach it. + +## Ten `/dk-*` commands + +Each one runs only when you ask for it. + +| Command | What it does | +| --- | --- | +| /dk-bootstrap | Fresh clone to a running install. | +| /dk-module | Plan a new module - dot-maker first. | +| /dk-route | Add a fully wired endpoint or page. | +| /dk-trace | Trace a request through pipeline, handler and response. | +| /dk-test | Write and run tests. | +| /dk-document | Write or update a feature doc. | +| /dk-check | Run and fix the QA gate. | +| /dk-deprecate | Make an evolution-pattern breaking change. | +| /dk-review | Pre-PR convention review - read-only by design. | +| /dk-hygiene | Encoding and line-ending audit. | + +## Seventeen skills, loaded on demand + +Sixteen `dotkernel-*` skills plus `dependency-policy`. +Each is a living document - when a review turns up the same mistake twice, that is a missing line in a skill. + +| Skill | Covers | +| --- | --- | +| application-variants | Detecting API vs Admin vs Frontend vs Light vs Queue. | +| module-structure | Where code goes: application module vs Core, and the wiring. | +| handler-naming | Both naming dialects, routes, authorization keys. | +| doctrine-entities | Entities, enums and DBAL types, repositories, migrations. | +| input-validation | InputFilters, Inputs, forms, CSRF, query whitelisting. | +| responses | HAL and collections, or templates and redirects; errors. | +| openapi | swagger-php attributes, for apps that publish OpenAPI. | +| feature-docs | Feature docs: template, where they live, staleness. | +| testing | Unit and functional patterns, test config, coverage matrix. | +| evolution-pattern | Sunset headers instead of versioning. | +| security | Auth, authorization, secrets, CORS, dependencies. | +| dot-maker | `composer make …` and the manual steps after it. | +| core-submodule | Core layering rules and git submodule mechanics. | +| psr-standards | PSR-1/3/4/6/7/11/12/15/16/17 as applied here. | +| qa-gate | cs-check, static analysis, tests, and forbidden "fixes". | +| troubleshooting | A symptom to cause table. | +| dependency-policy | The ladder from installed packages to hand-rolled code, and the proposal format. | + +A review subagent, `dotkernel-reviewer`, runs the convention review in its own context so the main one stays clean. + +## Never name a package from memory + +The ladder is: already in `composer.lock`, then `dotkernel/*`, then `laminas/*` and `mezzio/*`, then a vetted community package, and only then hand-rolled code. +Stop at the first hit. + +A skill's description decides whether it *can* load, not whether Claude stops to think before naming a package. +The rule that makes it reach for the skill has to be always loaded - which means it lives in your project's own `CLAUDE.md`, not in the skill. + +- [The block to paste](https://github.com/dotkernel/dotboost#add-this-to-your-projects-claudemd) + +### Three things about how that behaves + +- The `dotkernel/*` manifest is generated on first use, not shipped - the first package question runs the sync script itself. It needs `curl`, `jq` and network; without them Claude is told to call a package name **unverified** rather than assert it. +- Neither that script nor `composer show --available` is in the allow or deny list, so both prompt. That is deliberate - expect a prompt the first time. +- "How do I send mail from here?" is a package question. Implicit ones count. + +## Copy one directory into your project + +The payload has to sit at the root of your project. +Two routes to the same files - pick whichever suits the machine you are on. + +### 1 · Clone it inside the project + +Shallow, because only the current state of `main` is any use in a target project. +The clone goes away right after the copy. + +```shell +git clone --depth 1 \ + https://github.com/dotkernel/dotboost.git .dotboost +cp -r .dotboost/.claude .claude +rm -rf .dotboost +``` + +### 1b · Or download the zip + +No git needed, which is the point. +The archive unpacks to `dotboost-main/` and does carry the dotfiles. + +```shell +curl -L -o dotboost.zip https://github.com/\ +dotkernel/dotboost/archive/refs/heads/main.zip +unzip -q dotboost.zip +cp -r dotboost-main/.claude .claude +``` + +### 2 · Restore the executable bits + +Nothing is committed executable, and a zip extracted on Windows carries no permission bits at all. +The sync script is the one called by its own name. + +```shell +chmod +x .claude/hooks/*.sh .claude/statusline.sh \ + .claude/skills/dependency-policy/scripts/*.sh +``` + +### 3 · Keep local overrides out of git + +Then copy the example file if you want personal settings that survive an update. + +```shell +echo '.claude/settings.local.json' \ + >> .git/info/exclude +``` + +### 4 · Optional: markdown linting + +Not bundled - install it yourself, globally or as a project devDependency. +Until the binary resolves, the hook exits silently. + +```shell +npm install -g markdownlint-cli2 +``` + +### 5 · Paste the CLAUDE.md block + +The dependency policy and, if you want feature docs actually read, the feature-docs block alongside it. + +If the project already has its own `.claude/settings.json` or `.claude/commands/`, merge by hand rather than running `cp -r` blind - same-named files are overwritten. +To update later, run the whole install again over the top; anything you changed in the project's copy is lost, which is exactly why personal changes belong in `settings.local.json`. + +## The parts you notice on day two + +### Feature documentation — Docs · /dk-document + +The part a cleared session cannot reconstruct from `src/`. + +One markdown file per feature - what it does, why, the routes and the roles that reach them, the data added, how to exercise it. +Where it lands is detected, not assumed: `documentation/features/` when that directory exists, `docs/features/` otherwise, never a second documentation root beside an existing one. +The frontmatter is load-bearing - `/dk-review` greps `routes:` and `handlers:` to decide whether a new route in the diff is documented. + +### Real usage, not guesses — Status line + +The same numbers `/usage` reports. + +5h and 7d account usage in the status bar, read from the rate-limit payload Claude Code 2.1+ passes on stdin. +Pure bash and awk - no `jq`, no python, no transcript scanning or guessed token budgets, so it works in Git Bash on Windows. + +### A briefing, every time — Session start + +What it detected, before you ask anything. + +Which variant, the root namespace, the authorization style, the branch, which config files are still missing - and a CRLF warning when it finds one. +The same hook prints the feature-docs directory and a doc count, so the files are discoverable even without the CLAUDE.md block. + +### CRLF, handled — Windows · Line endings + +Dotkernel repos ship `* text eol=lf`. + +Belt and braces, configure the client so nothing converts: `core.autocrlf false`, `core.eol lf`, `core.safecrlf warn`. +Already committed CRLF? Renormalise once with `git add --renormalize .`. +`normalize-file.sh` keeps new writes clean. + +## Every variant, one payload + +The application-variants skill detects which Dotkernel application it is looking at - including a project derived from one - and every other skill applies the matching dialect. +Handler naming, response shape and authorization keys all differ between them, which is the whole reason the detection runs first. + +### Detected variants + +`API` `Admin` `Frontend` `Light` `Queue` + +Before adapting the skills to a new Dotkernel application, the advice from its maintainer is to spend an hour reading that repo and correcting them against what is actually there. +A skill written from framework docs rather than the codebase produces confident wrong answers, which is worse than no skill. + +### API — Variant · Detected + +Handlers, HAL collections, OpenAPI attributes. + +A REST API on a PSR-15 middleware pipeline, with OAuth 2.0, RBAC, HAL payloads and an OpenAPI 3.0 specification wired up on install. + +- [Read more](https://www.dotkernel.com/api/) +- [GitHub](https://github.com/dotkernel/api) + +### Admin — Variant · Detected + +Forms, CSRF, route-name authorization keys. + +Table-based record management with RBAC guards, CSRF-protected forms and 2FA, over the shared Core module. + +- [Read more](https://www.dotkernel.com/admin/) +- [GitHub](https://github.com/dotkernel/admin) + +### Frontend — Variant · Detected + +Action controllers and action-level guards. + +A web starter skeleton - user accounts, a contact form, sessions and RBAC-guarded controller actions, rendered on the server. + +- [Read more](https://www.dotkernel.com/frontend/) +- [GitHub](https://github.com/dotkernel/frontend) + +### Light — Variant · Detected + +Minimal modules, config-declared template routes. + +The smallest complete Mezzio application - routing, pipeline and Twig, six direct dependencies and no database layer. + +- [Read more](https://www.dotkernel.com/light/) +- [GitHub](https://github.com/dotkernel/light) + +### Queue — Variant · Detected + +Message handlers and background workers. + +Background workers on Symfony Messenger - a TCP listener, Valkey streams, retries and a dead letter queue for what still fails. + +- [Read more](https://www.dotkernel.com/queue/) +- [GitHub](https://github.com/dotkernel/queue) + +## Open source, in production + +Context your AI tools do not have to guess at. + +Dotboost is maintained by Borsan Sergiu and released as open source alongside the rest of the Dotkernel ecosystem. +Treat the skills as living documents - the repository is where corrections belong. + +[Read the full README →](https://github.com/dotkernel/dotboost) + + + + + + +--- +title: "Dotkernel Frontend | Server-rendered PHP web starter" +description: "Dotkernel Frontend is a web starter skeleton on Mezzio and Laminas - user accounts, a contact form, RBAC guards, CSRF-protected forms, flash messages and GDPR account anonymization, server-rendered in Twig." +canonical_url: "https://www.dotkernel.com/frontend/" +language: "en" +--- + +# Dotkernel Frontend + +Web starter · Server-rendered + +A web starter skeleton on the Mezzio microframework and Laminas components, for the applications people log into. +User accounts, a working contact form and a content page ship as proof of concept - real, running features whose only job is to show you where your own code goes. + +- [Read the docs](https://docs.dotkernel.org/frontend/) +- [View on GitHub](https://github.com/dotkernel/frontend) +- [Live demo](https://v5.dotkernel.net/) + +| | | +| --- | --- | +| Runtime | Mezzio + Laminas | +| Pattern | Action controllers | +| Templating | Twig | + +## Request lifecycle + +Session (`dot-session`) → Router (FastRoute) → Authentication (User identity) → RBAC guard (route + action) → Controller action (`dot-controller`) → Response (Twig + flash). + +## The web application half of the stack + +Frontend is the skeleton for a server-rendered application with users in it. +Where Light gives you pages, Frontend gives you the machinery around them: sessions, authentication, registration, validated forms, flash messages between redirects, and role-based access to individual controller actions. + +The shipped features - contact us, a generic content page, user accounts - are deliberately presented as building blocks rather than a finished product. +They exist to showcase the file architecture and to be copied. + +Extending the power of Mezzio by Laminas. + +- User accounts, from register to unregister +- Action controllers, not request handlers +- CSRF and reCAPTCHA on public forms +- GDPR anonymization out of the box + +## Everything a logged-in site needs first + +These are the parts you would otherwise spend your first two weeks assembling, already working together in a fresh install. + +### Registration & login — Users · Accounts + +The whole account lifecycle, already routed. + +Login, registration and account management, including avatar upload, password change and unregistering. +Password reset and account activation emails are part of the flow, which is why the skeleton stores a name and an email address and nothing more. + +### Guards per action — Security · Access control + +Permissions applied to individual controller actions. + +`dot-rbac-guard` and `dot-rbac` read roles and permissions from `authorization.global.php`, then `authorization-guards.global.php` maps rules onto a route and a named list of its actions - or an empty list to cover all of them. +Fine-grained without being scattered. + +### CSRF tokens — Security · Forms + +A new token per render, validated on submit. + +Built from the `laminas-form` CSRF element, a `laminas-session` CSRF validator in the input filter, and the `formElement` view helper in the template. +Tokens expire after a configurable timeout - one hour by default - and are never reusable between forms. + +### Contact form with reCAPTCHA — Public · Contact + +A public form that does not become a spam relay. + +The contact form uses Google reCAPTCHA, with the site and secret keys read from local configuration and the message recipients - `to` and any number of `cc` addresses - configured alongside them. +Whitelist `localhost` while developing, and take it out again for production. + +### Flash messages — UX · Feedback + +The message survives the redirect. + +`dot-flashmessenger` carries session messages across redirects - the small piece that makes post-then-redirect flows feel finished. +`dot-session` extends laminas-session underneath, configured in `session.global.php`. + +### Attribute injection — Wiring · DI + +Constructor injection declared on the constructor. + +`dot-dependency-injection` reads an `#[Inject]` attribute and resolves each listed dependency - a service, the whole `config` array, or a single key by dot notation. +Register the class against `AttributedServiceFactory` and stop writing factories. + +### Doctrine ORM — Data · Persistence + +Migrations and fixtures, driven from the CLI. + +Doctrine through `roave/psr-container-doctrine`, with UUIDs as a field type via `ramsey/uuid-doctrine`. +Migrations live in `data/doctrine/migrations`; `bin/doctrine fixtures:execute` seeds the default roles. + +### Headers & CORS — Delivery · HTTP + +Response headers declared per route. + +`dot-response-header` sets custom headers per route from `response-header.global.php`, while `mezzio-cors` handles origins, headers and cookies from `cors.global.php`. + +### Menus, templates, i18n — Content · Presentation + +Navigation from configuration, translation when you need it. + +`dot-navigation` defines and parses menus inside templates from configuration. +`dot-twigrenderer` adds Dotkernel's Twig extensions, and `laminas-i18n` is present for a complete translation suite. +The `Plugin` module carries dynamic forms and templates. + +## Account anonymization, not just deletion + +Under the GDPR, a company recording personal data from EU citizens must delete it on request - or anonymize it, which the European Commission accepts as an alternative. +Frontend implements the second option, because deleting a user row is rarely what your foreign keys want. + +The skeleton stores only what it needs to run those flows: first name, last name and the email address used as the identity, for password reset and account activation. +Anonymizing replaces exactly those. + +- [Anonymization reference](https://docs.dotkernel.org/frontend/v5/reference/account-anonymization/) + +### What anonymization changes + +- First and last name become `anonymous` plus the current UNIX timestamp - for example `anonymous1725980747`. +- The email becomes the same value plus whatever you set in `userAnonymizeAppend` - `anonymous1725980747@example.com`. +- The avatar image and its database record are deleted. + +Point `userAnonymizeAppend` at a domain you control and it doubles as a catch-all address, if your mail provider supports one. +Leave it empty and the local part stands alone. + +## Five modules, PSR-4 throughout + +Each module keeps its controllers, entities, repositories and services together, alongside a `ConfigProvider` and a `RoutesDelegator` - so a new feature has an obvious shape before you write it. + +### `User` + +Login, registration and account management - the largest module, and the one worth reading first. + +### `App` + +Core functionality: authentication, rendering and error reporting. + +### `Contact` + +The contact us form, from validation through to the outgoing mail. + +### `Page` + +Displaying a page - the minimal case, for static copy. + +### `Plugin` + +Plugin functionality for dynamic forms and templates. + +### Module contents + +`Controller`, `Entity`, `Repository` and `Service` folders, plus `InputFilter`, `EventListener`, `Helper`, `Command` or `Factory` as needed. + +## From clone to welcome page + +The documentation walks through every command with its expected output. +This is the sequence. + +### 1 · Clone into an empty folder + +Git refuses a non-empty directory, and you need write permissions on it. + +```shell +git clone https://github.com/dotkernel/frontend.git . +``` + +### 2 · Install dependencies + +From the CLI, so the prompts stay interactive. +Decline the config provider injection - Frontend ships its own. + +```shell +composer install +``` + +### 3 · Enable development mode + +Sets debug on, configuration caching off, and clears any existing cache. + +```shell +composer development-enable +``` + +### 4 · Prepare the config files + +Copy the `.dist` files into place - `local.php`, `development.local.php`, `mail.local.php`, `debugbar.local.php` - then fill in the database, SMTP and reCAPTCHA details. + +### 5 · Migrate and seed + +Migrations build the schema and are logged so none runs twice; the fixtures populate the default user roles. + +```shell +php vendor/bin/doctrine-migrations migrate +php bin/doctrine fixtures:execute +``` + +### 6 · Fix permissions and open it + +Three writable paths cover almost every first-run error. + +```shell +chmod -R 777 data log public/uploads +``` + +Two local-only notes worth keeping: `session.cookie_secure` has to be `false` in your own `local.php` - never in `local.php.dist`, where it stays `true` for production - and a stale `data/cache/config-cache.php` is loaded regardless of `ConfigAggregator::ENABLE_CACHE`, so clear it with `bin/clear-config-cache.php` when services go missing. +Duplicating `local.test.php.dist` gives your tests an in-memory database. + +## What the server needs + +| Component | Requirement | +| --- | --- | +| Operating system | A \*nix based system is strongly recommended for production. | +| PHP | 8.2 or newer, mod_php or FCGI (FPM). `memory_limit` at least 128M; `upload_max_filesize` and `post_max_size` at least 100M depending on your data. | +| Web server | Apache 2.2+ with `mod_rewrite` and `.htaccess` support (`AllowOverride All`); a default `.htaccess` ships in `public/`. On Nginx, translate it into server configuration. | +| Database | Tested with MariaDB 10.11 LTS and 11.4 LTS, and with MySQL 8.4 LTS. For MySQL 8.4, `my.cnf` needs `mysql_native_password=ON`. | +| Required extensions | `mbstring`, the CLI SAPI for cron jobs, and Composer on `$PATH`. | +| Recommended extensions | `opcache`; `pdo_mysql` or `mysqli`; `dom` and `simplexml` for markup; `gd` and `exif` for images; `zlib`, `zip`, `bz2` for compression; `curl` when calling APIs; `sqlite3` for the test suite. | + +Note that Frontend still supports MySQL - unlike API and Admin v7, which require native UUID support and therefore PostgreSQL or MariaDB 10.7+. + +## Where Frontend sits + +Frontend stands on its own, outside the Headless Platform. +It is the right starting point when your users log in and your pages are rendered on the server - and the wrong one in both directions from there. + +### Light — Smaller · Minimal + +No users, no database, no forms to protect. + +The smallest complete Mezzio application - routing, pipeline and Twig, six direct dependencies. +Right for a presentation site. + +- [Read more](https://www.dotkernel.com/light/) +- [Demo](https://light.dotkernel.net/) + +### API — Different · HTTP surface + +Your frontend is someone else's framework. + +A REST API on a PSR-15 middleware pipeline, with OAuth 2.0, RBAC, HAL payloads and an OpenAPI 3.0 specification wired up on install. + +- [Read more](https://www.dotkernel.com/api/) +- [Demo](https://api.dotkernel.net/) + +### Admin — Bigger · Platform + +You need a back office over a shared domain. + +Table-based record management with RBAC guards, CSRF-protected forms and 2FA, over a Core module shared with API and Queue. + +- [Read more](https://www.dotkernel.com/admin/) +- [Demo](https://admin7.dotkernel.net/) + +### Queue — Alongside · Async work + +Move the registration email off the request. + +Background workers on Symfony Messenger - a TCP listener, Valkey streams, retries and a dead letter queue for what still fails. + +- [Read more](https://www.dotkernel.com/queue/) +- [GitHub](https://github.com/dotkernel/queue) + +### Dotboost — Tooling · AI context + +Teach your AI tools this architecture. + +Drop-in Claude Code configuration - ten commands, seventeen skills and permission guardrails that keep your secrets out of the context window. + +- [Read more](https://www.dotkernel.com/dotboost/) +- [GitHub](https://github.com/dotkernel/dotboost) + +## Open source, in production + +Accounts, forms and compliance - already handled. + +Dotkernel Frontend is developed and led by the dev team at Apidemia and released as open source for the community. +Try the demo to see the account flows before you install anything. + +[Talk to us →](https://www.dotkernel.com/contact/) + + + + + + +--- +title: "Dotkernel Light | Minimal PSR-15 site skeleton for PHP" +description: "Dotkernel Light is a minimal PSR-15 application skeleton on Mezzio and Laminas, for presentation sites and small services. Six direct dependencies, Twig templating, Vite assets, no database layer." +canonical_url: "https://www.dotkernel.com/light/" +language: "en" +--- + +# Dotkernel Light + +Minimal skeleton · PSR-15 + +A PSR-15 compliant application skeleton on the Mezzio microframework and Laminas components, designed as a minimal project for generating a simple website - a presentation site, a landing page, a small service. +Six direct dependencies, two modules, and nothing to delete before you start. + +- [Read the docs](https://docs.dotkernel.org/light-documentation/) +- [View on GitHub](https://github.com/dotkernel/light) +- [Live demo](https://light.dotkernel.net/) + +| | | +| --- | --- | +| Runtime | Mezzio + Laminas | +| Templating | Twig | +| Direct deps | Six | + +## Request lifecycle + +`index.php` (bootstrap) → Container (factories · aliases) → Routing (FastRoute) → Pipeline (`pipeline.php`) → Handler (PSR-15) → Twig (layout · blocks) → Emitter (HtmlResponse). + +## The smallest complete Mezzio application + +Light is what remains when you take a real PSR-15 application and remove everything a presentation site does not need. +There is no ORM, no authentication stack, no admin scaffolding - not disabled, simply not installed. +What you get is routing, a middleware pipeline, Twig, and error logging. + +That makes it two things at once: a genuine starting point for a small site, and the clearest way to read how a Mezzio application is put together, because there is almost nothing else in the way. + +Extending the power of Mezzio by Laminas. + +- Six direct dependencies +- No database layer to configure +- A new page is one config line and one template +- Twig layout, Vite asset pipeline + +## Six packages, and you can name all of them + +This is the entire direct dependency list. +Every one of them is doing a job you would have had to solve anyway. + +### [mezzio/mezzio](https://github.com/mezzio/mezzio) + +The PSR-15 middleware microframework the whole application runs on. + +### [mezzio-fastroute](https://github.com/mezzio/mezzio-fastroute) + +FastRoute integration - matches the URL and method against your registered routes. + +### [mezzio-twigrenderer](https://github.com/mezzio/mezzio-twigrenderer) + +Twig integration for Mezzio. +Every template is a `.html.twig` file. + +### [dot-errorhandler](https://github.com/dotkernel/dot-errorhandler) + +Logging error handler for middleware applications - daily log files, configured in one place. + +### [laminas-config-aggregator](https://github.com/laminas/laminas-config-aggregator) + +Collects and merges configuration from every source into one array. + +### [laminas-component-installer](https://github.com/laminas/laminas-component-installer) + +The Composer plugin that injects modules and config providers during installation. + +Two modules ship in `src/`: `App` for core functionality - rendering and error reporting - and `Page` for displaying a page. +Each module keeps its `Handler`, `Factory` and `Service` folders next to a `ConfigProvider` and a `RoutesDelegator`, following PSR-4. + +## A request, end to end + +Eleven steps from the browser to the rendered page, with nothing hidden behind a kernel. +This is the sequence you can follow in the source. + +| # | Step | What happens | +| --- | --- | --- | +| 1 | HTTP request | `public/index.php` bootstraps the application, loads configuration, and creates the Mezzio application instance. | +| 2 | Service container | Factories, aliases and delegators are registered; every service is configured and ready to use. | +| 3 | Route registration | All available routes and their allowed methods are read and registered dynamically, managed by FastRoute. | +| 4 | Middleware pipeline | `config/pipeline.php` defines the order middleware runs in, and so how requests travel and responses come back. | +| 5 | Routing | FastRoute matches the URL and method against the registered routes to find the handler. | +| 6 | Handler invocation | The matched route name is pulled off the request and passed to the renderer - `page::about` becomes the template to render. | +| 7 | Custom logic | Your business logic runs in the handler, calling whatever services it needs. | +| 8 | Template rendering | Twig loads the template, applies the layout, renders the blocks and includes the partials. | +| 9 | Response creation | An `HtmlResponse` is built with status, headers and the rendered HTML body. | +| 10 | Response pipeline | The response flows back out through the middleware stack, which can still change headers, cookies or compression. | +| 11 | Response emitter | The final response is sent to the browser. | + +Handlers, not controllers - that is what keeps the application PSR-15 compliant. +Each module's `RoutesDelegator` reads the route config from the container and registers the routes it finds, so adding a page never means touching the framework. + +## Adding a page is two files + +Routes live in configuration as `slug => template` pairs. +Append a line, create the matching Twig template in `src/Page/templates/page/`, and add a link to the menu in the layout. +The handler works out which template to render from the matched route name, so there is no handler to write. + +Want your templates in more than one folder? +Add another entry under `paths` in the Page module's `ConfigProvider` - the key does not have to match the folder name. + +- [Creating pages](https://docs.dotkernel.org/light-documentation/v1/how-tos/create-pages/) + +### One route, one template + +In `config/autoload/local.php`, under the `routes` → `page` key: + +```php +'example-page' => 'example-template', +``` + +Then create the template it names: + +```text +src/Page/templates/page/example-template.html.twig +``` + +The key is the page slug, the value is the template. +Your new page answers at `/page/example-page`, under the route name `page::example-template`. + +## Assets, menus and social cards + +The parts you actually edit on a presentation site, and where each of them lives. + +### Asset pipeline — Build · Vite + +One command while you work, one before you ship. + +Vite concatenates and compresses CSS and JavaScript, preprocesses SCSS, and copies fonts and images - avoiding the network bottleneck of many separate files. +`npm run watch` recompiles on change; `npm run build` compiles once. +Node.js v20 is the minimum supported version. + +### assets → public — Build · Source of truth + +Edit the source, never the output. + +Images, fonts, JavaScript and SCSS live in `src/App/assets/`. +The build deletes and rebuilds `css`, `js`, `fonts` and `images` under `public/` - anything you edit there by hand is lost on the next run. +Everything else in `public/` is left alone. + +### Cache busting — Build · Caching + +The oldest bug in web deployment, solved in one character. + +Browsers cache your built CSS and JS, so a deploy can leave visitors on the old file. +Add a version parameter to the asset URL in the layout - `app.css?v=3` - and increment it whenever you commit a change to that file. + +### Menu and footer — Content · Navigation + +Plain Twig in one layout file. + +The top menu is the list under `id="navbarHeader"` in `src/App/templates/layout/default.html.twig`; each `li` is one item, and items can be grouped into dropdowns or styled as buttons. +The footer is the `app-footer` element in the same file. + +### Twitter & OpenGraph — Content · Sharing + +Cards for when your pages get posted elsewhere. + +Add the card meta tags to the layout head, generating URLs with the same helper the canonical block uses - `{{ url(routeName ?? null) }}` - which also keeps mistyped URLs from breaking the page. + +### Errors and logs — Operations · Logs + +A log file per day, from the first request. + +`dot-errorhandler` writes daily files into `log/`, in the format set under the `stream` key of `error-handling.global.php`. +Development mode adds the error handlers you want locally and nowhere else. + +## Running in minutes, honestly + +No database to create, no fixtures to seed. +Clone, install, set a URL, open it. + +### 1 · Clone into an empty folder + +Git refuses a directory that is not empty, and you need write permissions on it. + +```shell +git clone https://github.com/dotkernel/light.git . +``` + +### 2 · Install dependencies + +Run it from the CLI so the prompts stay interactive. +Decline the config provider injection - Light already includes its own. + +```shell +composer install +``` + +### 3 · Enable development mode + +Local work only. +`composer development-status` reports where you stand. + +```shell +composer development-enable +``` + +### 4 · Set the base URL + +Point `$baseUrl` in `config/autoload/local.php` at your virtual host. + +### 5 · Fix the writable folders + +The two directories the application writes to. +Most first-run errors are this and nothing else. + +```shell +chmod -R 777 ./data ./log +``` + +### 6 · Open it in a browser + +The Dotkernel Light welcome page is waiting. +Errors about missing services usually mean a stale config cache. + +```shell +php ./bin/clear-config-cache.php +``` + +A cached `data/cache/config-cache.php` is loaded regardless of the `ConfigAggregator::ENABLE_CACHE` setting - which is exactly why clearing it fixes so much. +On Windows, WSL2 with AlmaLinux is the recommended development environment. + +## What the server needs + +| Component | Requirement | +| --- | --- | +| Operating system | A \*nix based system is strongly recommended for production. | +| PHP | 8.2, 8.3 or 8.4, with mod_php or FCGI (FPM). `memory_limit` at least 128M. | +| Web server | Apache 2.2+ with `mod_rewrite` and `.htaccess` support (`AllowOverride All`); a default `.htaccess` ships in `public/`. On Nginx, translate it into server configuration. | +| Database | None. Light has no persistence layer - which is the point. | +| Required extensions | `mbstring`, plus Composer available on `$PATH`. | +| Recommended extensions | `opcache`; `dom` and `simplexml` for markup; `gd` and `exif` for images; `zlib`, `zip`, `bz2` for compression; `curl` if you call APIs. | + +## When Light is the wrong answer + +Light sits outside the Headless Platform on purpose. +It has no sessions, no forms, no authentication and no database - so the moment you need those, start from a skeleton that already has them rather than growing them here. + +### Frontend — Instead · Full-stack web + +You need logins, forms and sessions. + +A server-rendered web starter on Mezzio and Laminas - Twig views, forms, sessions and RBAC-guarded routes, still standing on its own outside the platform. + +- [Read more](https://www.dotkernel.com/frontend/) +- [GitHub](https://github.com/dotkernel/frontend) +- [Demo](https://v5.dotkernel.net/) + +### API — Instead · HTTP surface + +You are serving clients, not pages. + +A REST API on a PSR-15 middleware pipeline, with OAuth 2.0, RBAC, HAL payloads and an OpenAPI 3.0 specification wired up on install. + +- [Read more](https://www.dotkernel.com/api/) +- [GitHub](https://github.com/dotkernel/api) + +### Admin — Instead · Back office + +You need to manage records, not publish copy. + +Table-based record management with RBAC guards, CSRF-protected forms and 2FA, over a Core module shared with the rest of the platform. + +- [Read more](https://www.dotkernel.com/admin/) +- [GitHub](https://github.com/dotkernel/admin) + +### Queue — Instead · Async work + +You have work that outlives a request. + +Background workers on Symfony Messenger - a TCP listener, Valkey streams, retries and a dead letter queue for what still fails. + +- [Read more](https://www.dotkernel.com/queue/) +- [GitHub](https://github.com/dotkernel/queue) + +### dotboost — Tooling · AI context + +Teach your AI tools this architecture. + +Drop-in Claude Code configuration - ten commands, seventeen skills and permission guardrails that keep your secrets out of the context window. + +- [Read more](https://www.dotkernel.com/dotboost/) +- [GitHub](https://github.com/dotkernel/dotboost) + +## Open source, in production + +Small enough to read, real enough to ship. + +Dotkernel Light is developed and led by the dev team at Apidemia, and released as open source for the community. +If you want to see the whole application before you commit to it, that is the entire promise of this skeleton. + +[Talk to us →](https://www.dotkernel.com/contact/) + + + + + + +--- +title: "Dotkernel Queue | Asynchronous task queue for PHP" +description: "Dotkernel Queue runs time-consuming work on background workers instead of inside the request cycle. A Swoole TCP daemon, Valkey streams and Symfony Messenger, with retries, a dead letter queue and reporting commands." +canonical_url: "https://www.dotkernel.com/queue/" +language: "en" +--- + +# Dotkernel Queue + +Background workers · Symfony Messenger + +Some operations are time-consuming and resource-intensive, and they have no business inside a request. +Dotkernel Queue accepts them over a TCP connection, stores them, and runs them on background workers - so your platform answers the next request instead of waiting for a video to finish encoding. + +- [Read the docs](https://docs.dotkernel.org/queue-documentation/) +- [View on GitHub](https://github.com/dotkernel/queue) + +| | | +| --- | --- | +| Listener | Swoole on TCP | +| Store | Valkey streams | +| Worker | Symfony Messenger | + +## Message lifecycle + +Your application (JSON payload) → TCP :8556 (IP whitelisted) → Swoole daemon (accepts & stores) → messages stream (Valkey · FIFO) → Messenger worker (your handler) → failed stream (after 3 retries). + +## A separate machine for the slow work + +Dotkernel Queue is built on **Symfony Messenger**, integrated into Mezzio and Laminas applications through the `netglue/laminas-messenger` adapter for the Laminas Service Manager container. +Work runs decoupled from the regular request-response cycle, on background workers that can live on their own hardware. + +The main platform returns its response and stays responsive to new requests. +Tasks with long execution times are scheduled to run when resources are available, oldest first. + +Extending the power of Mezzio by Laminas. + +- High request rate without overloading +- FIFO - the oldest message runs first +- Retries with exponential backoff +- Failures parked, never blocking the queue + +## Work that should never block a response + +Tasks suited to the queue take extended periods to execute and may be interrupted by PHP limits such as `max_execution_time`; depend on external systems that add authentication delays, slow replies, or fail outright when a server is offline; or simply are not part of the PHP response at all. + +### Data processing + +Big data analytics, scientific simulations, mathematical computations - anything where the data size or the algorithm sets the clock. + +### File & media processing + +Video and image processing, compressing and decompressing large files. + +### Networking + +Uploading and downloading large files, where bandwidth rather than your code is the limit. + +### Database operations + +Imports, exports and migrations that would otherwise hold a web request open. + +### System & infrastructure + +OS updates, software compilation, CI pipelines triggered from your application. + +### Transactional email + +The classic case: hand the message to the queue and let the worker compose and send it while the server moves to the next task. + +[Sending emails →](https://docs.dotkernel.org/queue-documentation/v2/how-to/send-emails/) + +## Two daemons, one stream + +A listener that accepts messages fast, and a worker that processes them carefully. +They are separate systemd services, so you can restart either one without losing the other. + +### The TCP listener — Ingest · Swoole + +Accepts connections and gets out of the way. + +An active daemon listens for TCP connections on a port - 8556 by default - and stores incoming messages immediately. +This supports a large number of requests per second without overloading, because accepting a message is all it does. + +### The messages stream — Store · Valkey + +An open-source key/value datastore built for this shape of work. + +Messages land in the `messages` stream, the main queue. +The worker consumes from it in FIFO order - the oldest request first, then newer ones - and processes each according to your application logic. +Valkey is BSD-licensed and handles caching, message queues, and primary datastore workloads. + +### The worker — Process · Messenger + +Your handler, running outside the request cycle. + +A Messenger worker consumes the stream and hands each message to your handler. +Because it is a separate service on a separate machine if you want one, a slow job costs you worker time rather than user-facing latency. + +### Dead letter queue — Resilience · DLQ + +Failures get parked, not retried forever. + +Each transport defines a retry strategy. +When a message exceeds its allowed retries it is forwarded automatically to the failure transport and stored in the `failed` stream - so a message that cannot succeed never blocks the ones behind it. + +### Whitelisted senders — Security · Firewall + +The port is open to your servers, not to the internet. + +The documented setup adds a firewall rich rule that accepts traffic on the queue's port only from the source addresses you name. +Fast to evaluate, and a small attack surface for a service that takes instructions. + +### Metrics you can query — Observability · Logs + +Logging that answers the operational questions. + +Processed and failed messages are logged, which is what makes the reporting commands possible: queue length, processing time per job, error rates, and throughput in jobs per second. + +## Backoff, configured in one place + +The transport's `retry_strategy` decides how hard the worker tries before a message is handed to the dead letter queue. +These are the shipped defaults. + +| Setting | Default | What it controls | +| --- | --- | --- | +| `max_retries` | 3 | Attempts before the message moves to the failure transport. | +| `delay` | 1000 | Initial wait before retrying a failed message, in milliseconds. | +| `multiplier` | 2 | Each retry's delay is multiplied by this factor - exponential backoff. | +| `max_delay` | 0 | Ceiling on the wait between retries; 0 means unlimited or default behaviour. | +| `failure_transport` | `failed` | Where messages go once they exceed the retry limit. | + +Both transports are Redis-protocol DSNs pointing at a stream - `messages` for new work, `failed` for what could not be processed - with the serializer configurable per transport. + +## Console commands, over CLI or TCP + +The same three commands answer from the shell on the queue server or from a socket on another machine. +Every flag is optional; when both `--start` and `--end` are given, `--limit` is ignored. + +| Command | Returns | Invocation | +| --- | --- | --- | +| `failed` | Log entries for messages that failed to process. | `php bin/cli.php failed --start="yyyy-mm-dd" --end="yyyy-mm-dd" --limit=int` | +| `processed` | Log entries for messages processed successfully. | `php bin/cli.php processed --start="yyyy-mm-dd" --end="yyyy-mm-dd" --limit=int` | +| `inventory` | Everything currently queued in the `messages` stream. | `php bin/cli.php inventory` | +| `control` | A test message, logged as processed - the quickest end-to-end check. | `echo "control" \| socat -t1 - TCP:host:port` | + +Valkey itself stays inspectable: `valkey-cli` gives you `PING`, `INFO`, `KEYS *`, and stream commands such as `XRANGE streamName - +` to read entries oldest to newest, or `XTRIM streamName MAXLEN 0` to empty a stream while keeping the key. + +## Two ways to hand over a message + +The **procedural** approach opens a TCP connection, writes the JSON payload, and closes the socket. +It is simple and quick to implement - and harder to reuse as the project grows. +Make sure the message ends with a newline, or the server will keep waiting for the rest of it. + +The **object-oriented** approach wraps the queue in a service - a small `NotificationSystem` module under Core, built on `clue/socket-raw`, with its connection details injected from configuration. +Handlers then inject the service and call a method that describes the intent, not the transport. + +- [Integration guide](https://docs.dotkernel.org/queue-documentation/v2/how-to/communication-with-queue/) + +### Smoke-test the listener + +From your local machine, with the daemon running and your IP whitelisted: + +```shell +echo "Hello" | socat -T1 - TCP:SERVER-IP:8556 +``` + +The `-T1` timeout is optional but wise: without it, a server that never replies leaves `socat` waiting indefinitely. + +## A queue server, from a bare box + +The documented path is AlmaLinux 9 or 10 with root access, adapted accordingly for other operating systems. +Everything runs as a non-root user. + +### 1 · Prepare the server + +Update the OS, create a user with sudo permissions, and install the utilities you will need - including `socat` for testing. + +```shell +dnf update -y && useradd dotkernel +``` + +### 2 · Install the runtime + +PHP from the Remi repository, plus the Swoole and Redis PECL extensions. +Verify both are loaded before moving on. + +```shell +dnf module enable php:remi-8.5 +dnf install php-pecl-swoole6 php-pecl-redis +``` + +### 3 · Install Valkey + +Enable and start the service, then confirm it answers. + +```shell +dnf install valkey +valkey-cli ping +``` + +### 4 · Clone and configure + +Clone the queue branch, then copy each `.dist` configuration file into place - local, log, messenger and swoole - and fill them in. + +```shell +git clone -b default-queue https://github.com/dotkernel/queue.git +composer install --no-dev +``` + +### 5 · Register the daemons + +Set the paths in the shipped unit files, copy them into `/etc/systemd/system/`, then enable and start both. + +```shell +systemctl enable --now swoole.service +systemctl enable --now messenger.service +``` + +### 6 · Close the door + +Allow SSH before starting the firewall, then permit the queue port only from the addresses that should reach it. + +```shell +firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="YOUR_IP" port port="8556" protocol="tcp" accept' +``` + +Full commands, expected output and the reasoning behind each step are in the [server setup](https://docs.dotkernel.org/queue-documentation/v2/server-setup/) and [installation](https://docs.dotkernel.org/queue-documentation/v2/installation/) guides. + +## The third deployable + +Queue processes work produced by the rest of the platform. +Share the `Core` module with it - copied in or added as a submodule - and your worker has the same entities, services and mail configuration as the application that queued the job. + +### Shared domain layer + +`Core\Admin` `Core\App` `Core\Security` `Core\Setting` `Core\User` + +Keep Core in sync between the main project and the queue and every class, service and configuration needed for message processing is already there - which is what makes a worker able to compose an email from your own templates. + +### API — Pair with · HTTP surface + +Dispatch work from an endpoint, answer immediately. + +A REST API on a PSR-15 middleware pipeline, with OAuth 2.0, RBAC, HAL payloads and an OpenAPI 3.0 specification wired up on install. + +- [Read more](https://www.dotkernel.com/api/) +- [GitHub](https://github.com/dotkernel/api) + +### Admin — Pair with · Back office + +Queue a bulk operation from an admin screen. + +Table-based record management with RBAC guards, CSRF-protected forms and 2FA, over the same Core module. + +- [Read more](https://www.dotkernel.com/admin/) +- [GitHub](https://github.com/dotkernel/admin) + +### Frontend — Pair with · Public-facing + +Queue the email your signup form triggers. + +A web starter skeleton - user accounts, a contact form, sessions and RBAC-guarded controller actions, rendered on the server. + +- [Read more](https://www.dotkernel.com/frontend/) +- [Demo](https://v5.dotkernel.net/) + +### Light — Smaller · Minimal + +A site with nothing to run in the background. + +The smallest complete Mezzio application - routing, pipeline and Twig, six direct dependencies and no database layer. + +- [Read more](https://www.dotkernel.com/light/) +- [Demo](https://light.dotkernel.net/) + +### Dotboost — Tooling · AI context + +Teach your AI tools this architecture. + +Drop-in Claude Code configuration - ten commands, seventeen skills and permission guardrails that keep your secrets out of the context window. + +- [Read more](https://www.dotkernel.com/dotboost/) +- [GitHub](https://github.com/dotkernel/dotboost) + +The queue's own dependencies are the same small `dot-*` components the rest of the platform uses - each publishing its own support status on the [packages lifecycle](https://www.dotkernel.com/dotkernel-packages-oss-lifecycle/) page. + +## Open source, in production + +Let the slow work happen somewhere else. + +Dotkernel Queue is developed and led by the dev team at Apidemia - built to keep real platforms responsive under real load, and released as open source for the community. + +[Talk to us →](https://www.dotkernel.com/contact/) + + + + + + +--- +title: "Who We Are" +canonical_url: "https://www.dotkernel.com/who-we-are/" +language: "en" +--- + +# demo page + +WHO WE ARE + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. +Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + +## [heading](https://docs.mezzio.dev/mezzio/) + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. +Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + +## [heading](https://github.com/laminas/laminas-diactoros) + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. +Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + +## [heading](https://github.com/laminas/laminas-stratigility) + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. +Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + diff --git a/public/llms.txt b/public/llms.txt index bba26afc..bf4dee55 100644 --- a/public/llms.txt +++ b/public/llms.txt @@ -1,6 +1,6 @@ # Dotkernel Light -> Dotkernel Light is the technical blog for the Dotkernel headless PHP platform — a PSR-15 compliant application built on Mezzio and Laminas components. It publishes architecture write-ups, how-tos, and release notes for Dotkernel Admin, API, Frontend, and Light. +> Dotkernel Light is the technical blog for the Dotkernel headless PHP platform — a PSR-15 compliant application built on Mezzio and Laminas components. It publishes architecture write-ups, how-tos, and release notes for the platform applications — Dotkernel API, Admin and Queue — and for the standalone Dotkernel Light skeleton. Content spans foundational PHP/middleware architecture (PSR-7, PSR-15, request lifecycle, dependency injection), practical how-tos (Doctrine migrations, CORS, authentication, caching), and the history/release notes of the Dotkernel ecosystem going back to 2008. Posts are organized by category and attributed to an author; URLs follow the pattern `/{category-slug}/{post-slug}/`. @@ -8,8 +8,7 @@ Content spans foundational PHP/middleware architecture (PSR-7, PSR-15, request l - [Blog](https://www.dotkernel.com/blog/): full list of posts, most recent first, paginated - [Categories](https://www.dotkernel.com/categories/): all categories with post counts -- [About](https://www.dotkernel.com/about/): what Dotkernel Light is -- [Who We Are](https://www.dotkernel.com/who-we-are/): team/company background +- [About](https://www.dotkernel.com/about/): the team behind Dotkernel — how the team works, its commitment to open source and the PHP community, and how it uses AI under guardrails - [OSS Package Lifecycle](https://www.dotkernel.com/dotkernel-packages-oss-lifecycle/): support/maintenance status of Dotkernel's open-source packages - [Contact](https://www.dotkernel.com/contact/) diff --git a/public/md-pages/about.md b/public/md-pages/about.md new file mode 100644 index 00000000..595587a5 --- /dev/null +++ b/public/md-pages/about.md @@ -0,0 +1,140 @@ +--- +title: "About us | The team behind Dotkernel" +description: "Dotkernel is built by the dev team at Apidemia - a team that reviews every change together, gives its work back to the PHP community under MIT, and puts guardrails around the tools it adopts." +canonical_url: "https://www.dotkernel.com/about/" +language: "en" +--- + +# The team behind Dotkernel + +About us · Apidemia + +Dotkernel started as an internal tool for handling complex architectures, built because we needed it ourselves. +We released it under MIT because the PHP community gave us the components it stands on. +Everything here is made by a team that has to live with its own decisions. + +- [Talk to us](https://www.dotkernel.com/contact/) +- [View on GitHub](https://github.com/dotkernel) +- [Join the discussion](https://github.com/orgs/dotkernel/discussions) + +| | | +| --- | --- | +| Team | Apidemia | +| License | MIT | +| Released continuously since | 2018 | + +## How a change ships + +Propose (plan before code) → Build (house structure) → Review (another pair of eyes) → QA gate (cs · PHPStan · tests) → Document (for the next person) → Release (MIT, in public). + +## A team that shares the whole codebase + +Nobody here owns a private corner of the code. +Every module follows the same structure, every handler is named the same way, and every application is assembled from the same explicit wiring - so any developer on the team can open any part of the platform and know where they are. +That is a deliberate design choice, and it is what makes reviewing each other's work possible rather than polite. + +Getting better at this is part of the job, not something we hope happens after hours. +Our developers read the upstream source of the components we depend on, write the documentation the next person will need, and turn every repeated mistake into a written rule instead of a reprimand. +When a review catches the same thing twice, that is a gap in what we have written down - and we fix the writing. + +Extending the power of Mezzio by Laminas. + +- Every change reviewed by someone else +- One structure across every application +- Learning time counted as real work +- Decisions written down, not remembered + +## Six things we are not willing to trade + +None of these are aspirations we printed on a wall. +Each one shows up somewhere you can check - in a repository, in a license, in a config file, or in the way a release is handled. + +### Better together than alone — How we work · Teamwork + +The codebase is the team's, not a collection of territories. + +Work is planned out loud before it is written, reviewed before it is merged, and documented before it is called done. +Consistency is what makes that cheap: because a module in one application looks like a module in every other, a reviewer spends their attention on the decision being made rather than on finding their way around. + +### Developers who keep growing — People · Improvement + +Nobody stays where they started. + +Our developers learn by reading the source of the components they build on, by writing the guides and reference docs the rest of the team relies on, and by taking the review notes that come back to them as material rather than criticism. +Every breaking change we publish ships with the article explaining it - writing that explanation is how we make sure we actually understood the change. + +### Giving back to PHP — Community · Open source + +MIT licensed, and answered in public. + +Dotkernel stands on Mezzio, Laminas and Doctrine, so our work goes back out under a permissive license with nothing held behind a paid tier. +The team answers questions on our own components and on the Laminas ones underneath them, publishes the support status of every `dot-*` package instead of leaving it to guesswork, and keeps the discussion where anyone can read it. + +- [Packages lifecycle](https://www.dotkernel.com/dotkernel-packages-oss-lifecycle/) +- [Discussions](https://github.com/orgs/dotkernel/discussions) + +### Every need you can imagine — Customers · Delivery + +The unglamorous requirement is the one that decides a project. + +Our skeletons come out of real client work, which is why they answer questions nobody asks in a demo: sharing a database with an application that was there first, running several connections at once, anonymizing an account instead of deleting the row, hearing about a bug that never threw an exception. +We would rather carry that detail for you than let you discover it in your second sprint. + +### AI with guardrails on — Tooling · Responsible AI + +Adopted everywhere, trusted nowhere by default. + +AI assistance runs through every part of how we work, under rules we wrote down first. +Secrets are never loaded rather than merely redacted, so credentials and signing keys stay out of the context window entirely. +Sessions start by proposing a plan instead of editing files. +Linters report and never rewrite. +A package is never named from memory - it is verified against what is actually installed. +And a human reviews every diff, because accountability does not delegate. + +- [How we do it](https://www.dotkernel.com/dotboost/) + +### Best product, best tools — Craft · Quality + +Chosen for what they will still be worth in three years. + +We build on interfaces from the PHP Framework Interop Group so a component from one vendor can be replaced by another, keep static analysis at a strict level, ship the test suite with the skeleton rather than promising it later, and stay current with the PHP versions our dependencies support. +When a tool stops being the right answer, we say so - publicly, with a migration path. + +- [See the architecture](https://www.dotkernel.com/architecture/) + +## Where you can check any of this + +### The QA gate + +Coding standards, static analysis at a strict rule level and the test suite all run before a change is merged - and "make the checker quiet" is never an accepted fix. + +### Documentation as a deliverable + +A feature is not finished until the next developer can pick it up from what is written, which is why [docs.dotkernel.org](https://docs.dotkernel.org/) is maintained alongside the code. + +### Deprecations, not surprises + +Endpoints announce their own retirement through response headers, so a client finds out from the API rather than from a changelog nobody read. + +### Support status in public + +Every package publishes where it sits in its lifecycle, so choosing one is never a bet on whether it is still maintained. + +### Secrets stay out of the tooling + +Local configuration, environment files and OAuth keys are unreadable to our AI tooling by configuration, not by convention. + +### Supported by JetBrains + +Our open-source tooling is supported through the [JetBrains open source programme](https://jb.gg/OpenSourceSupport) - one of the ways this work stays sustainable. + +[Work with us →](https://www.dotkernel.com/contact/) + +## Open source, in production + +Built by a team that has to live with its own decisions. + +Dotkernel is developed and led by the dev team at Apidemia. +If you are weighing up a platform, hiring a team, or just want to argue about middleware, we would rather have the conversation than the brochure. + +[Talk to us →](https://www.dotkernel.com/contact/) diff --git a/public/md-pages/admin.md b/public/md-pages/admin.md new file mode 100644 index 00000000..aeeba797 --- /dev/null +++ b/public/md-pages/admin.md @@ -0,0 +1,314 @@ +--- +title: "Dotkernel Admin | Open-source PHP admin application" +description: "Dotkernel Admin is an open-source admin application (skeleton) built on Mezzio, Laminas and Doctrine. Table-based record management, RBAC guards, CSRF-protected forms and 2FA, over the same Core as Dotkernel API." +canonical_url: "https://www.dotkernel.com/admin/" +language: "en" +--- + +# Dotkernel Admin + +Admin application · Server-rendered + +An open-source application skeleton for standing up the administration site behind your platform. +A fast, reliable way to manage the records in your database with a simple table-based approach, and to build the reports and graphs that let you monitor what is happening - with the graphical components for an intuitive experience already in place. + +- [Read the docs](https://docs.dotkernel.org/admin-documentation/) +- [View on GitHub](https://github.com/dotkernel/admin) +- [Live demo](https://admin7.dotkernel.net/) + +| | | +| --- | --- | +| Runtime | Mezzio + Laminas | +| Templating | Twig | +| License | MIT | + +## Request lifecycle + +Session (`dot-session`) → Router (`dot-router`) → Authentication (`laminas-authentication`) → RBAC guard (`dot-rbac-guard`) → Your handler (PSR-15) → Response (Twig template). + +## The back office, not a framework to learn + +Dotkernel Admin is the complementary application of the Dotkernel Headless Platform: an independent app built on the same Mezzio and Laminas foundation as Dotkernel API, sharing the same tech stack so the two form one consistent system rather than two codebases that merely talk to each other. + +Authentication, role-based access control, CSRF-protected forms, validation, navigation and a dashboard are already assembled. +What you add is your own screens for your own entities. + +Extending the power of Mezzio by Laminas. + +- Table-based record management +- Every shipped form validated and CSRF-guarded +- Shares the Core module with API +- Twig templates, npm asset pipeline + +## The unglamorous parts, already done + +Everything below works in a fresh install. +The default modules are configured; your custom functionality needs the same configuration entries, and the docs say exactly which ones. + +### RBAC guards — Security · Access control + +Permissions declared in config, enforced per route handler. + +`dot-rbac` and `dot-rbac-guard` work together: roles and their permissions live in `authorization.global.php`, while `authorization-guards.global.php` maps each route handler to the permissions it requires. +Add a route, add its rule - access control never drifts into your handlers. + +### TOTP two-factor — Security · 2FA + +A password plus a code that expires in 30 seconds. + +`dot-totp` adds time-based one-time passwords following the industry standard: administrators authenticate with their password and a 6-digit code from an authenticator app. +Installation is a documented set of forms, handlers, middleware and three new columns. + +- [Install guide](https://docs.dotkernel.org/admin-documentation/v7/tutorials/install-dot-totp/) + +### CSRF protection — Security · Forms + +A fresh token per render, on every form that ships. + +Built from the `laminas-form` CSRF element, the `laminas-session` CSRF validator and the `formElement` view helper. +Tokens are not reusable between forms and expire after a configurable timeout, defaulting to one hour. + +### Forms & input filters — Input · Validation + +Validation rules that live beside the form, not in the handler. + +`laminas-form` bridges your domain models and the view layer, while `laminas-inputfilter` normalizes and validates the submitted set. +Every form in the skeleton has its inputs filtered, so your own forms have a pattern to copy. + +### Doctrine ORM — Data · Persistence + +Migrations and fixtures, both driven from the CLI. + +Doctrine ORM 3 and DBAL 4 over MariaDB or PostgreSQL, with UUID identifiers via `ramsey/uuid-doctrine`. +Generate a migration with `doctrine-migrations diff`, apply it with `migrate`, then seed the tables with `bin/doctrine fixtures:execute`. + +### Attribute injection — Wiring · DI + +Constructor injection declared where the constructor is. + +`dot-dependency-injection` reads an `#[Inject]` attribute on the constructor and resolves each listed dependency - including values from a configuration key, by dot notation. +Register the class against `AttributedServiceFactory` and you are done writing factories by hand. + +### Menus & UI components — Interface · Navigation + +Configuration-driven menus, ready-made interface pieces. + +`dot-navigation` defines and parses the top menu from `navigation.global.php`. +The `Page` module carries reusable dropdowns, modal popups, error displays and tooltips; the `Dashboard` module holds the landing page layout and its widgets. + +### Commands & lock files — Operations · CLI + +Cron-safe console commands out of the box. + +`dot-cli` builds the console application on top of laminas-cli, writing lock files into `data/lock` so a scheduled command cannot overlap with itself. +GeoLite2 databases sync through `bin/cli.php geoip:synchronize`, quiet mode included for cron jobs. + +### Mail and error handling — Operations · Mail & logs + +Transactional mail configured in one file, errors in another. + +`dot-mail` handles sendmail or SMTP delivery, sender identity and CC lists from `mail.global.php`. +`dot-errorhandler` writes daily log files to `log/`, with the format set in `error-handling.global.php`. + +## Modules you can name before you read them + +Admin follows PSR-4, with one folder per module. +Each module keeps its handlers, input filters and services together, plus a `ConfigProvider` and a `RoutesDelegator` - so the second module you write looks like the first. + +### Shared domain layer + +`Core\Admin` `Core\App` `Core\Security` `Core\Setting` `Core\User` + +The Core module is a common codebase shared with the other applications in your project. +Each submodule holds its entities, repositories and its own ConfigProvider, so a user means the same thing to the admin screen and to the API endpoint. + +### `Admin` + +Managing the users that hold the `admin` role - the accounts stored in the `admin` database table. + +### `User` + +Managing the platform's own users, stored separately in the `user` table. +Two audiences, two models, no overloaded role column. + +### `App` + +Core application functionality: authentication, rendering and error reporting. + +### `Dashboard` + +The default landing page - its layout, widgets and rendering logic. + +### `Page` + +Reusable interface elements: dropdowns, modals, error displays, tooltips. + +### `Setting` + +Saving and reading display settings for the administration interface. + +## From clone to login page + +The full walkthrough, with expected output for every command, is in the documentation. +This is the shape of it. + +### 1 · Clone the project + +Into an empty directory of your choosing. + +```shell +git clone https://github.com/dotkernel/admin.git . +``` + +### 2 · Install dependencies + +Run it from the CLI so the setup prompts stay interactive. +Decline the ConfigProvider injection - Dotkernel ships its own. + +```shell +composer install +``` + +### 3 · Enable development mode + +For local work only. +`composer development-status` tells you where you stand, and it must stay off in production. + +```shell +composer development-enable +``` + +### 4 · Configure the database + +Fill in the credentials in `config/autoload/local.php`. +Both a MariaDB and a PostgreSQL connection are pre-declared; one is active at a time. + +### 5 · Migrate and seed + +Migrations build the schema; fixtures populate the default roles, OAuth clients and accounts. + +```shell +php ./vendor/bin/doctrine-migrations migrate +php ./bin/doctrine fixtures:execute +``` + +### 6 · Build the assets + +`npm run watch` recompiles while you work; `npm run prod` minifies for release. + +```shell +npm install && npm run prod +``` + +Open the virtual host in your browser and the Dotkernel Admin login page is waiting. +If the fixtures ran, sign in with user `admin` and password `dotadmin` - the same credentials as the public demo, and the first thing to change before going live. + +## What the server needs + +| Component | Requirement | +| --- | --- | +| Operating system | A \*nix based system is strongly recommended for production. | +| PHP | 8.2 or newer, mod_php or FCGI (FPM). `memory_limit` at least 128M; `upload_max_filesize` and `post_max_size` at least 100M depending on your data. | +| Web server | Apache 2.2+ with `mod_rewrite` and `.htaccess` support (`AllowOverride All`) - a default `.htaccess` ships in `public/`. On Nginx, translate it into server configuration. | +| Database | MariaDB 10.7, 10.11 LTS, 11.4 LTS and 11.8 LTS, or PostgreSQL 13 and above. **MySQL is not supported**, as it has no UUID support. | +| Required extensions | `mbstring`, the CLI SAPI for cron jobs, and Composer available on `$PATH`. | +| Recommended extensions | `opcache`; `pdo_mysql`, `pdo_pgsql` or `mysqli` to match your database; `dom` and `simplexml` for markup; `gd` and `exif` for images; `zlib`, `zip`, `bz2` for compression; `curl` when calling APIs; `sqlite3` for the test suite. | + +On Windows, WSL2 is the recommended development environment - Dotkernel provides an AlmaLinux distro implementation and an install script for the whole local stack. + +## The checklist nobody writes down + +Admin gives you the tools to build a safe application, and the documentation is explicit that some of them are yours to apply. +These are the ones worth checking off before the first deploy. + +- [Security guide](https://docs.dotkernel.org/admin-documentation/v7/security/basic-security/) + +### Before you go to production + +- Change or remove the demo admin account - its identity and password are public. +- Review `cookie_httponly`, `cookie_samesite` and `cookie_secure` in `session.global.php`. +- Confirm development mode is off with `composer development-status`. +- Set your allowed origins in `cors.global.php`. +- Keep secrets in `*.local.php` - the `*.global.php` and `*.php.dist` files are committed. +- Add CSRF fields and authorization rules to every form and route you add. +- Watch dependencies on both sides: Composer advisories and `npm audit`. + +## Upgrades you can read before you run them + +Admin does not ship an automatic upgrade path, and that is deliberate: you implement the modifications listed for each release, in a codebase you already understand, instead of handing a migration script control over your customizations. + +A `CHANGELOG.md` in the project root lists implemented features in reverse chronological order, so it doubles as your version marker. +From version 6.2 onward, the procedure is documented version to version, and releases are also published as an RSS feed. + +- [Releases](https://github.com/dotkernel/admin/releases) +- [Upgrade guide](https://docs.dotkernel.org/admin-documentation/v7/upgrading/upgrading/) + +### Coming from Admin 6.x? + +Version 7 bumped dependencies, synchronised the Core module and refreshed the codebase to stay compatible with Dotkernel API v7 - the release that moved identifiers to native database UUIDs and dropped MySQL. + +Work through the pull requests listed for 7.0, then copy the release notes into your own changelog so your project keeps tracking upstream. + +## Better with an API in front of it + +Admin installs and runs on its own. +Paired with Dotkernel API over the same `Core` namespaces, Admin manages the data while the API exposes it to third-party frontends and backends - and neither one disagrees about what an entity is. +The rest of the ecosystem is below. + +### API — Pair with · HTTP surface + +Expose the data you manage here to any client. + +A REST API on a PSR-15 middleware pipeline, with OAuth 2.0, RBAC, HAL payloads and an OpenAPI 3.0 specification wired up on install. + +- [Read more](https://www.dotkernel.com/api/) +- [GitHub](https://github.com/dotkernel/api) +- [Demo](https://api.dotkernel.net/) + +### Queue — Pair with · Async work + +Queue a bulk operation from an admin screen. + +Background workers on Symfony Messenger - a TCP listener, Valkey streams, retries and a dead letter queue for what still fails. + +- [Read more](https://www.dotkernel.com/queue/) +- [GitHub](https://github.com/dotkernel/queue) + +### Frontend — Instead · Public-facing + +The site your users log into, not your staff. + +A web starter skeleton - user accounts, a contact form, sessions and RBAC-guarded controller actions, rendered on the server. + +- [Read more](https://www.dotkernel.com/frontend/) +- [Demo](https://v5.dotkernel.net/) + +### Light — Smaller · Minimal + +No records to manage, just pages. + +The smallest complete Mezzio application - routing, pipeline and Twig, six direct dependencies and no database layer. + +- [Read more](https://www.dotkernel.com/light/) +- [Demo](https://light.dotkernel.net/) + +### Dotboost — Tooling · AI context + +Teach your AI tools this architecture. + +Drop-in Claude Code configuration - ten commands, seventeen skills and permission guardrails that keep your secrets out of the context window. + +- [Read more](https://www.dotkernel.com/dotboost/) +- [GitHub](https://github.com/dotkernel/dotboost) + +New modules start with [dot-maker](https://docs.dotkernel.org/dot-maker/v1/overview/), which knows which files an Admin needs versus an API. +Support status for every layer is on the [packages lifecycle](https://www.dotkernel.com/dotkernel-packages-oss-lifecycle/) page. + +## Open source, in production + +Try it with the demo, keep it under MIT. + +Sign in to the public demo with `admin` / `dotadmin` to see the interface before you install anything. +Dotkernel Admin is developed and led by the dev team at Apidemia, and released as open source for the community. + +[Talk to us →](https://www.dotkernel.com/contact/) diff --git a/public/md-pages/api.md b/public/md-pages/api.md new file mode 100644 index 00000000..7a5c3737 --- /dev/null +++ b/public/md-pages/api.md @@ -0,0 +1,304 @@ +--- +title: "Dotkernel API | Open-source REST API skeleton for PHP" +description: "Dotkernel API is an open-source REST API skeleton built on Mezzio, Laminas and Doctrine. OAuth2, RBAC, HAL, problem details and OpenAPI 3.0 come wired together on install. MIT licensed." +canonical_url: "https://www.dotkernel.com/api/" +language: "en" +--- + +# Dotkernel API + +REST API skeleton · PSR-15 middleware + +An open-source REST API skeleton for PHP, built on the Mezzio microframework and Laminas components over a Doctrine domain layer. +OAuth 2.0, RBAC authorization, HAL payloads, standardized error responses and an OpenAPI 3.0 specification are assembled on install - not left as decisions for your first sprint. + +- [Read the docs](https://docs.dotkernel.org/api-documentation/) +- [View on GitHub](https://github.com/dotkernel/api) +- [Live demo](https://api.dotkernel.net/) + +| | | +| --- | --- | +| Runtime | Mezzio + Laminas | +| Persistence | Doctrine ORM | +| License | MIT | + +## Request lifecycle + +CORS (`mezzio-cors`) → Router (FastRoute) → Authentication (OAuth 2.0) → RBAC guard (per route name) → Your handler (PSR-15) → Response (HAL / problem+json). +[All 19 stages](https://www.dotkernel.com/architecture/) + +## A REST API you own from the first commit + +Dotkernel API is the root of the Dotkernel Headless Platform: a REST API based on the Mezzio skeleton, using a PSR-compliant middleware stack as defined by the PHP Framework Interop Group. +Rather than relying on elements built into a framework, business logic is built explicitly - from handlers to dependencies - so you keep full control over your business logic and data architecture. + +It scales down to a single microservice and up to an enterprise-grade API, and it has been developed and released continuously since 2018. + +Extending the power of Mezzio by Laminas. + +- Doctrine ORM, not Active Record +- Explicit wiring, no runtime magic +- Auth, docs and errors on install +- MIT licensed, actively maintained + +## The parts every API needs, already connected + +Each of these is configured and working in a fresh install. +Nothing here is a placeholder you have to research and assemble before you can serve your first authenticated endpoint. + +### OAuth 2.0 — Security · Authentication + +Bearer tokens, issued and validated by the API itself. + +Authentication runs on `mezzio-authentication-oauth2`, which wraps `league/oauth2-server` to provide OAuth 2.0 for PSR-7 / PSR-15 applications. +Endpoints marked as authenticated require a valid Bearer token and answer `401 Unauthorized` without one. + +### RBAC per route — Security · Authorization + +Three levels of protection: none, authenticated, authorized. + +Roles and their permissions are declared in configuration and allocated per route name, with role inheritance supported. +An authenticated request that lacks the required permission gets a `403 Forbidden` - decided by config, not scattered through your handlers. + +### OpenAPI 3.0 — Contract · Documentation + +A machine-readable contract, generated from the code that serves it. + +Every module documents its endpoints in an `OpenAPI.php` file, which `zircote/swagger-php` turns into a specification rendered through Swagger UI or Redoc. +A Postman collection and environment ship alongside it, so every endpoint is ready to call by hand. + +### Doctrine ORM — Data · Persistence + +Your domain in entities, not in the query layer. + +Doctrine ORM handles persistence, so you can focus on object-oriented business logic and treat storage as a secondary concern. +Migrations, fixtures via `dot-data-fixtures`, and multiple database connections are part of the setup. + +### HAL & problem details — Contract · Responses + +One predictable shape for resources, one for failures. + +Payloads are built with `mezzio-hal`, describing each resource together with its relational links and embedded child resources. +Errors return problem details responses via `mezzio-problem-details`, giving clients standardized error codes system-wide. + +### Content negotiation — Contract · Negotiation + +Client and server agree on format and language up front. + +Content negotiation is implemented out of the box through the `Content-Type` and `Accept` headers, so diverse systems can consume the same API without custom glue on either side. +CORS preflight requests are recognized and configured by `mezzio-cors`. + +### Error reporting endpoint — Operations · Feedback + +A channel for the bugs that never reach your logs. + +Frontend developers can report incorrect behaviour by posting to `/error-report` with a token header. +The API validates the request against configured tokens, domains and IPs before logging it - useful precisely when nothing fatal was thrown. + +### Commands & file locker — Operations · CLI + +Scheduled work that refuses to trample itself. + +Console commands are registered through `dot-cli` on top of Symfony Console, and `php ./bin/cli.php route:list` prints every endpoint the application exposes. +The file locker, enabled by default, writes a lock file per command so a second instance cannot start while the previous run is still going - the safeguard cron jobs usually lack. + +### Transactional email — Operations · Mail + +Account mail that works before you configure anything. + +`dot-mail` wraps Symfony Mailer with templated messages, so account activation and password reset send out of a fresh install. +It joins the request flow only where it is needed - the rest of the pipeline is unchanged whether a route sends mail or not. + +### dot-maker — Productivity · Scaffolding + +New modules that already match the house style. + +`dot-maker` generates project files and directories following the Dotkernel structure, and knows which files each application type needs. +It replaces hand-copied boilerplate with consistent, standardized scaffolding. + +- [Docs](https://docs.dotkernel.org/dot-maker/v1/overview/) + +## What version 7 changed + +The latest release moves identifiers to native database UUIDs and broadens platform support. + +### Native UUID v7 + +Identifier columns use the database's `uuid` type instead of `binary`, with values generated by `ramsey/uuid`. +You keep full control of the UUID version without depending on database extensions. + +### PostgreSQL support + +PostgreSQL joins the supported databases. +Because native UUID is required, you need PostgreSQL or MariaDB 10.7 or later; MySQL is no longer supported, as it has no UUID data type. + +### PHP 8.5 + +The API targets PHP 8.5, with Dotkernel Admin on 8.4. +Dependencies are kept current, and the ecosystem's own packages track the versions Laminas and Doctrine support. + +### Table prefixes + +A configurable string can be prepended to every table name - the practical requirement when an API shares a database with an existing application. + +### Clearer database configuration + +Multi-connection setups spell out which connection is the default and how to switch to another, based on scenarios from real projects. + +### Shared Core module + +Common logic lives in a Core module kept as its own Git repository, which can be added as a submodule to any Dotkernel application so entities and queries stay consistent. + +## Evolution, not a wall of versions + +Dotkernel API favours an evolution pattern with a sunsetting mechanism over maintaining parallel API versions. +The same codebase evolves gradually and clients are given notice, instead of every change forking into another branch you have to keep alive. + +The mechanism is `DeprecationMiddleware`: mark a handler with the `#[ResourceDeprecation]` attribute and the response carries `sunset` and `link` headers, so a client learns an endpoint is retiring from the endpoint itself rather than from a changelog it never read. + +Full versioning stays reserved for major, format-level changes - the cases where it genuinely earns its maintenance cost. +The two approaches are not mutually exclusive. + +- [API evolution guide](https://docs.dotkernel.org/api-documentation/v6/tutorials/api-evolution/) +- [Where it sits in the pipeline](https://www.dotkernel.com/architecture/) + +### Coming from Laminas API Tools? + +API Tools (formerly Apigility) is archived. +Dotkernel API is an actively maintained alternative with a middleware architecture, a permissive MIT license and a Doctrine data layer. + +The comparison below is drawn from the full side-by-side write-up on our blog. + +## Dotkernel API vs Laminas API Tools + +| Feature | API Tools (formerly Apigility) | Dotkernel API | +| --- | --- | --- | +| First release | 2012 | 2018 | +| Architecture | MVC, event driven | Middleware | +| OSS lifecycle | Archived | Active | +| PHP version | ≤ 8.2 | See the PHP version badge for `dotkernel/api` | +| Style | REST, RPC | REST | +| Change management | Versioning | Deprecations (API evolution) | +| Documentation | Swagger (automated) | OpenAPI 3.0 (Swagger) and Postman (manual) | +| License | BSD-3 | MIT | +| Default DB layer | laminas-db | doctrine-orm 3.x | +| Authorization | ACL | RBAC guard | +| Authentication | HTTP Basic / Digest, OAuth 2.0 | OAuth 2.0 | +| Endpoint generator | Yes | dot-maker | +| PSR standards | PSR-7 | PSR-7, PSR-15 | + +Comparison drawn against Dotkernel API v7. +Read the reasoning behind each row in [Dotkernel API versus Laminas API Tools](https://www.dotkernel.com/dotkernel-api/dotkernel-api-versus-laminas-api-tools/). + +## Runs alone, or as one of three + +Dotkernel API is a complete application on its own. +When you add Admin or Queue, all three declare the same `Core` namespaces, so a `User` means the same thing to the endpoint that creates it, the admin screen that moderates it, and the worker that emails it. +The rest of the ecosystem is below. + +### Shared domain layer + +`Core\App` `Core\Admin` `Core\User` `Core\Security` `Core\Setting` + +One set of entities and repositories, committed in each repository so a single-component start just works. +Having many entities in Core does not oblige you to implement handlers for all of them - each application handles only what it needs. + +### Admin — Pair with · Back office + +Manage the same data your API serves. + +Table-based record management with RBAC guards, CSRF-protected forms and 2FA, over the same Core module. +Start with either one and add the other later. + +- [Read more](https://www.dotkernel.com/admin/) +- [GitHub](https://github.com/dotkernel/admin) +- [Demo](https://admin7.dotkernel.net/) + +### Queue — Pair with · Async work + +Move slow work off the request cycle. + +Background workers on Symfony Messenger - a TCP listener, Valkey streams, retries and a dead letter queue for what still fails. + +- [Read more](https://www.dotkernel.com/queue/) +- [GitHub](https://github.com/dotkernel/queue) + +### Frontend — Instead · Server-rendered + +When your users want pages, not payloads. + +A web starter skeleton - user accounts, a contact form, sessions and RBAC-guarded controller actions, rendered on the server. + +- [Read more](https://www.dotkernel.com/frontend/) +- [Demo](https://v5.dotkernel.net/) + +### Light — Smaller · Minimal + +A presentation site with no API behind it. + +The smallest complete Mezzio application - routing, pipeline and Twig, six direct dependencies and no database layer. + +- [Read more](https://www.dotkernel.com/light/) +- [Demo](https://light.dotkernel.net/) + +### Dotboost — Tooling · AI context + +Teach your AI tools this architecture. + +Drop-in Claude Code configuration - ten commands, seventeen skills and permission guardrails that keep your secrets out of the context window. + +- [Read more](https://www.dotkernel.com/dotboost/) +- [GitHub](https://github.com/dotkernel/dotboost) + +Every layer is also available on its own - see the [packages lifecycle](https://www.dotkernel.com/dotkernel-packages-oss-lifecycle/) for the support status of each `dot-*` package. + +## Built on interfaces, checked on every change + +### [PSR-15 & PSR-7](https://www.php-fig.org/psr/psr-15/) + +Handlers implement `RequestHandlerInterface` and return `ResponseInterface`. +The whole request path is middleware you can read top to bottom. + +### [PSR-11 & PSR-4](https://www.php-fig.org/psr/psr-11/) + +The application is container-based, with dependencies declared in each module's `ConfigProvider`, and classes located by autoloader. + +### [PSR-3 logging](https://www.php-fig.org/psr/psr-3/) + +Errors are logged through `LoggerInterface` via `dot-errorhandler`, centralizing how failures are recorded. + +### [PHPStan at level 8](https://phpstan.org/) + +Static analysis runs at a strict rule level, in line with the choice made by projects like Doctrine and Composer. + +### Functional & unit tests + +The skeleton ships with a test suite you extend rather than start, so new endpoints have somewhere to be tested from day one. + +### An active community + +Updates arrive with bugfixes and improvements from the PHP community, and breaking changes come with companion articles and upgrade steps. + +[Join the discussion →](https://github.com/orgs/dotkernel/discussions) + +## Install it and call an endpoint + +Create the project with Composer, point it at PostgreSQL or MariaDB 10.7+, run the migrations, and you have an authenticated REST API with a browsable OpenAPI specification. + +- [Installation guide](https://docs.dotkernel.org/api-documentation/) +- [Try the demo](https://api.dotkernel.net/) + +### composer create-project dotkernel/api + +The installer walks through database credentials and the initial configuration. +Follow the installation guide for the exact command and post-install steps for your target version, then use `dot-maker` to scaffold your first module. + +Questions along the way go to [Dotkernel Discussions](https://github.com/orgs/dotkernel/discussions) - the team answers issues on both Dotkernel and Laminas components. + +## Open source, in production + +An API foundation you can read, audit and keep. + +Dotkernel API is developed and led by the dev team at Apidemia - built first as an internal tool for handling complex architectures, released under MIT as our way of giving back to the community. + +[Talk to us →](https://www.dotkernel.com/contact/) diff --git a/public/md-pages/architecture.md b/public/md-pages/architecture.md new file mode 100644 index 00000000..592ec989 --- /dev/null +++ b/public/md-pages/architecture.md @@ -0,0 +1,282 @@ +--- +title: "Architecture | How a request moves through a Dotkernel application" +description: "The PSR-15 middleware pipeline behind Dotkernel API, stage by stage: error boundary, routing, content negotiation, deprecation headers, OAuth 2.0, RBAC and dispatch - plus the library flow underneath it." +canonical_url: "https://www.dotkernel.com/architecture/" +language: "en" +--- + +# How a request moves through a Dotkernel application + +Architecture · PSR-15 pipeline + +Every Dotkernel application is a stack of PSR-15 middleware with no framework runtime hidden underneath it. +A request enters at the error boundary, passes through routing, negotiation, authentication and authorization, and reaches your handler - and you can read every layer it crossed on the way. + +- [Read the docs](https://docs.dotkernel.org/api-documentation/) +- [See pipeline.php](https://github.com/dotkernel/api/blob/7.0/config/pipeline.php) +- [Dotkernel API](https://www.dotkernel.com/api/) + +| | | +| --- | --- | +| Contract | PSR-7 + PSR-15 | +| Container | PSR-11 | +| Logging | PSR-3 | + +## Five phases, 19 stages + +Error boundary (1 stage) → Request preparation (4 stages) → Routing (4 stages) → Contract & headers (4 stages) → Identity (2 stages) → Dispatch & fallback (4 stages). + +## Framework-less, not framework-free + +Dotkernel is an opinionated, framework-less tool aimed at intermediate-to-advanced programmers. +Framework-less does not mean nothing is underneath: Mezzio and Laminas components are there, and they are excellent. +It means there is no framework runtime deciding things on your behalf. +The pipeline is a list you wrote, in a file you can open, executed top to bottom. + +That is the whole trade. +You give up the convenience of conventions you never have to look at, and you get an application where every layer between the socket and your handler is named, ordered and yours to reorder. + +Extending the power of Mezzio by Laminas. + +- Gradual learning curve +- Fine-tuned security +- KISS, YAGNI, DRY +- Long-term support + +## Nineteen stages, in the order they run + +This is the default pipeline of Dotkernel API, unedited. +Each stage receives the request, may hand it onward, and may alter the response on the way back out - which is why the error boundary is first in and last out. + +Legend: Mezzio / Laminas component · Dotkernel. + +### Error boundary + +#### 01 · ProblemDetailsMiddleware — mezzio/mezzio-problem-details + +The outermost layer, deliberately. +Anything thrown deeper in the stack is handed to `dot-errorhandler` for logging and comes back as a problem details response, so a failure reaches the client as a documented error shape rather than a stack trace. + +### Request preparation + +#### 02 · MalformedRequestBodyMiddleware — dotkernel/api + +Rejects a body that is not valid JSON up front, before any later stage tries to read it and fails in a less legible place. + +#### 03 · BodyParamsMiddleware — mezzio/mezzio-helpers + +Parses the raw request body into the parsed body according to its `Content-Type`. + +#### 04 · ServerUrlMiddleware — mezzio/mezzio-helpers + +Seeds the server URL helper with the current scheme and host, so anything generating a URL later produces an absolute one. + +#### 05 · CorsMiddleware — mezzio/mezzio-cors + +Recognises preflight requests and answers them, and attaches the configured CORS headers to everything else. +It sits before routing so a preflight never needs a matched route. + +### Routing + +#### 06 · RouteMiddleware + +*mezzio/mezzio-router* — Route + +Matches the request against the route table and registers the `RouteResult` attribute. +Everything below this line can ask which route was matched - and most of it does. + +#### 07 · ImplicitHeadMiddleware — mezzio/mezzio-router + +Answers `HEAD` for routes that only declare `GET`. + +#### 08 · ImplicitOptionsMiddleware — mezzio/mezzio-router + +Answers `OPTIONS` with the methods the matched path actually allows. + +#### 09 · MethodNotAllowedMiddleware — mezzio/mezzio-router + +Returns `405 Method Not Allowed` when the path matched but the method did not. +Order matters here: it has to sit after both implicit handlers, or it would answer the requests they exist to serve. + +### Contract & headers + +#### 10 · ContentNegotiationMiddleware — dotkernel/api + +Reconciles the client's `Accept` and `Content-Type` with what the matched route is configured to serve, so diverse consumers share one API without custom glue. + +#### 11 · DeprecationMiddleware — dotkernel/api + +The mechanism behind API evolution. +It reads the `#[ResourceDeprecation]` attribute off the handler and, when one is present, sets the `sunset` and `link` response headers - the client learns an endpoint is going away from the endpoint itself, rather than from a migration guide. + +#### 12 · ResponseHeaderMiddleware — dotkernel/dot-response-header + +Applies the headers you configure application-wide to every outgoing response, in one place instead of per handler. + +#### 13 · UrlHelperMiddleware — mezzio/mezzio-helpers + +Seeds the URL helper with the routing result, so handlers and HAL links can be generated relative to the route that was actually matched. + +### Identity + +#### 14 · AuthenticationMiddleware + +*dotkernel/api* — 401 + +Validates the OAuth 2.0 Bearer token. +Routes declared as authenticated answer `401 Unauthorized` without a valid one, and never reach the handler. + +#### 15 · AuthorizationMiddleware + +*dotkernel/api* — 403 + +Checks the authenticated identity's role against the permissions allocated to the matched route name, and returns `403 Forbidden` when it falls short. +The decision lives in configuration, not scattered through handlers. + +### Dispatch & fallback + +#### 16 · ResourceProviderMiddleware — dotkernel/api + +Reads the `#[Resource]` attribute on the handler's `handle()` method and loads the named entity through Doctrine using the route placeholder. +The handler receives a resource rather than an identifier, and a record that does not exist becomes a `404` before your code runs. + +#### 17 · DispatchMiddleware — mezzio/mezzio-router + +Hands the request to the matched PSR-15 handler. +This is your code - everything above was getting the request into a state where your handler only has to do its own job. + +#### 18 · ProblemDetailsNotFoundHandler — mezzio/mezzio-problem-details + +Reached only when nothing above returned a response: emits `404` in problem details form. + +#### 19 · GetNotFoundResourceHandler — dotkernel/api + +The last stage in the pipeline, shaping the not-found response for API clients. + +## The packages underneath, in the same order + +The same journey seen one level down - which library owns each step. +The second column shows how little changes when a feature is added: sending an email inserts one package and leaves the rest of the flow alone. + +### Default flow + +A request that reads or writes data and returns a resource. + +1. Request +2. mezzio/mezzio-problem-details +3. dotkernel/dot-errorhandler +4. mezzio/mezzio-cors +5. mezzio/mezzio-router +6. dotkernel/dot-response-header +7. mezzio/mezzio-helpers +8. mezzio/mezzio-authentication +9. mezzio/mezzio-authorization +10. mezzio/mezzio-router *dispatch* +11. laminas/laminas-diactoros *not found* +12. dotkernel/dot-dependency-injection +13. doctrine/orm +14. mezzio/mezzio-hal +15. laminas/laminas-diactoros +16. psr/http-message +17. Response + +### Flow for sending an email + +Identical until the domain layer, where `dot-mail` joins in. + +1. Request +2. mezzio/mezzio-problem-details +3. dotkernel/dot-errorhandler +4. mezzio/mezzio-cors +5. mezzio/mezzio-router +6. dotkernel/dot-response-header +7. mezzio/mezzio-helpers +8. mezzio/mezzio-authentication +9. mezzio/mezzio-authorization +10. mezzio/mezzio-router *dispatch* +11. laminas/laminas-diactoros *not found* +12. dotkernel/dot-dependency-injection +13. doctrine/orm +14. dotkernel/dot-mail *added* +15. mezzio/mezzio-hal +16. laminas/laminas-diactoros +17. psr/http-message +18. Response + +## Built on interfaces from the Framework Interop Group + +Dotkernel applications are collections of PSR-7 middleware. +The standards below are not badges - they are the reason a package from one vendor drops into a pipeline built by another. + +### [PSR-7](https://www.php-fig.org/psr/psr-7/) + +HTTP messages are immutable value objects. +Every stage above receives a request and returns a response, and neither is a global you have to guard. + +### [PSR-15](https://www.php-fig.org/psr/psr-15/) + +Middleware and request handlers share one interface, which is what makes the pipeline a plain ordered list rather than a framework-specific event graph. + +### [PSR-11](https://www.php-fig.org/psr/psr-11/) + +The application is container-based, with each module declaring its dependencies in a `ConfigProvider`. + +### [PSR-4](https://www.php-fig.org/psr/psr-4/) + +Classes are located by autoloader from their namespace, so where a file lives is never a question of configuration. + +### [PSR-3](https://www.php-fig.org/psr/psr-3/) + +Errors are logged through `LoggerInterface` via `dot-errorhandler`, reached from the error boundary at stage one. + +### Why it matters + +You can delete any stage in the pipeline, or write your own and pipe it in, without asking a framework's permission. +That is the practical payoff of standardising on interfaces. + +[All PSR standards →](https://www.php-fig.org/psr/) + +## One pipeline shape, three applications + +The stages differ where the job differs - Admin adds sessions and CSRF-protected forms, Queue swaps HTTP dispatch for a message consumer - but the pattern is constant, and all three declare the same `Core` namespaces underneath. + +### Shared domain layer + +`Core\App` `Core\Admin` `Core\User` `Core\Security` `Core\Setting` + +Kept as its own Git repository and added as a submodule, so an entity means the same thing to the endpoint that creates it, the admin screen that moderates it and the worker that emails it. + +### [API](https://www.dotkernel.com/api/) + +The pipeline documented above, in full. + +### [Admin](https://www.dotkernel.com/admin/) + +Same shape, plus sessions, CSRF-protected forms and 2FA in the identity phase. + +### [Queue](https://www.dotkernel.com/queue/) + +The same middleware thinking applied to messages instead of requests. + +## The pipeline is one file + +Everything on this page comes from `config/pipeline.php` - about ninety lines, most of them comments explaining where to add your own middleware. +There is no generated layer between that file and what runs. + +- [Installation guide](https://docs.dotkernel.org/api-documentation/) +- [Try the demo](https://api.dotkernel.net/) + +### php ./bin/cli.php route:list + +Once the pipeline has routed a request, the route table is the other half of the picture. +This command lists every endpoint the application exposes, which is the fastest way to review an API you have just inherited. + +Database migrations are generated and run the same way, through the Doctrine console commands registered by `dot-cli`. + +## Open source, in production + +No layer you are not allowed to read. + +Dotkernel is developed and led by the dev team at Apidemia - built first as an internal tool for handling complex architectures, released under MIT as our way of giving back to the community. + +[Talk to us →](https://www.dotkernel.com/contact/) diff --git a/public/md-pages/dotboost.md b/public/md-pages/dotboost.md new file mode 100644 index 00000000..38d70084 --- /dev/null +++ b/public/md-pages/dotboost.md @@ -0,0 +1,331 @@ +--- +title: "dotboost | Claude Code configuration for Dotkernel projects" +description: "dotboost is a drop-in .claude/ directory that teaches Claude Code the conventions of any Dotkernel application - ten commands, seventeen skills, six hooks, and permission guardrails that keep secrets out of the context." +canonical_url: "https://www.dotkernel.com/dotboost/" +language: "en" +--- + +# Dotboost + +Developer tooling · AI context + +Drop-in configuration that teaches Claude Code the conventions of any Dotkernel application - API, Admin, Frontend, Light, Queue, or a project derived from one of them. +Every skill detects the variant first and applies the matching dialect, so generated code follows your architecture instead of guessing at it. + +- [View on GitHub](https://github.com/dotkernel/dotboost) +- [Claude Code docs](https://docs.claude.com/en/docs/claude-code/overview) + +| | | +| --- | --- | +| Payload | One .claude/ directory | +| Commands | Ten /dk-* | +| Skills | Seventeen | + +## What happens in a session + +Session start (variant briefing) → Skill loads (by description) → Plan mode (proposes first) → Guard hooks (Edit · Bash) → Normalise (BOM · CRLF · EOF) → Lint (`php -l` · markdown). + +## Not an application - a configuration payload + +Dotboost is not itself a Dotkernel application. +Its entire payload is the `.claude/` directory: settings, hooks, a status line, a review subagent, ten `/dk-*` commands and seventeen skills. +You install it by copying that directory into the Dotkernel project you are working on. + +There is no routing table telling Claude which skill to pick - each one loads from its own description. +Ask where a new Doctrine entity goes and it should reach for the module-structure skill rather than answering from general framework knowledge. + +Maintained by **Borsan Sergiu**. + +- Detects the variant before it answers +- Secrets never enter the context window +- Guards catch compound commands globs miss +- Copy it in - nothing to build or install + +## The reason to adopt the file + +The permission rules in `settings.json` fall into four tiers. +The first one is the headline: these are `Read` denies, not write protection, so the contents never enter the context window at all. + +### Your secrets — Tier · Never read + +Not redacted - never loaded. + +`.env`, every `*.local.php`, `config/autoload/local.php`, `local.test.php` and `data/oauth/`. +Database credentials, OAuth signing keys and environment secrets stay out of the transcript. + +### Generated & vendored — Tier · Never written + +The files a diff should never touch by accident. + +Dependency manifests - `composer.json`, `composer.lock`, `package.json`, `package-lock.json` - plus `vendor/`, `node_modules/`, `data/`, `log/`, `public/uploads/` and anything under `Migration/`. + +### Installs & destructive git — Tier · Never run + +Commands that change your tree without a diff. + +`composer require`, `remove`, `update`, `install`, `global`; the npm, yarn and pnpm install verbs; `git push`, `reset --hard`, `clean`, `submodule`; and `rm -rf`. + +### Your call — Tier · Ask & allow + +Preference, not policy - override per machine. + +**Ask**: migrations, `bin/doctrine`, database clients, git commits and merges, pipeline and authorization config, the QA config files. +**Allow**: the Composer QA scripts, `vendor/bin` tools, `php -l`, and read-only git. +Layer changes in `settings.local.json`, never the shared file. + +### Where globs cannot see — Hooks · Six + +Path patterns miss what a shell can hide. + +`guard-bash.sh` inspects the command itself, so `cd src && composer require foo` is caught where a path-based rule would let it through. +`guard-protected-paths.sh` covers edits, and redirects a blocked `*.local.php` edit to its `.dist` template. + +### Linters that do not rewrite — Hooks · Report only + +Deliberately no `phpcbf`, no `--fix`. + +`php-lint.sh` and `markdown-lint.sh` report and stop, because reformatting a file right after it is written invalidates the in-memory copy and breaks the next targeted edit. +Bulk formatting belongs at the end of a task. +Both skip silently when their tool is missing. + +`settings.json` is a committed file carrying opinions, not only guardrails: sessions start in **plan mode**, the terminal UI is full screen, the theme is dark, and Composer runs without a memory ceiling. +Know what you are adopting. + +## Where the hooks overrule the tiers + +Some commands marked *ask* in `settings.json` are refused outright by `guard-bash.sh`, because the hook sees compound commands the permission globs cannot. +The prompt you would expect never appears - you get a refusal. +This is deliberate, and worth knowing before it surprises you. + +| Command | settings.json says | guard-bash.sh does | +| --- | --- | --- | +| git rebase | ask | blocks | +| doctrine-migrations migrate / execute | ask | blocks | +| fixtures:execute, schema:drop, schema:update | ask, via bin/cli.php | blocks | +| composer development-enable / -disable | — | blocks | +| pip install, git checkout --, git filter-branch | — | blocks | + +Everything else under *ask* - commits, database clients, the config and QA files - prompts as documented. +To get a blocked command back you edit the hook; relaxing `settings.local.json` will not reach it. + +## Ten `/dk-*` commands + +Each one runs only when you ask for it. + +| Command | What it does | +| --- | --- | +| /dk-bootstrap | Fresh clone to a running install. | +| /dk-module | Plan a new module - dot-maker first. | +| /dk-route | Add a fully wired endpoint or page. | +| /dk-trace | Trace a request through pipeline, handler and response. | +| /dk-test | Write and run tests. | +| /dk-document | Write or update a feature doc. | +| /dk-check | Run and fix the QA gate. | +| /dk-deprecate | Make an evolution-pattern breaking change. | +| /dk-review | Pre-PR convention review - read-only by design. | +| /dk-hygiene | Encoding and line-ending audit. | + +## Seventeen skills, loaded on demand + +Sixteen `dotkernel-*` skills plus `dependency-policy`. +Each is a living document - when a review turns up the same mistake twice, that is a missing line in a skill. + +| Skill | Covers | +| --- | --- | +| application-variants | Detecting API vs Admin vs Frontend vs Light vs Queue. | +| module-structure | Where code goes: application module vs Core, and the wiring. | +| handler-naming | Both naming dialects, routes, authorization keys. | +| doctrine-entities | Entities, enums and DBAL types, repositories, migrations. | +| input-validation | InputFilters, Inputs, forms, CSRF, query whitelisting. | +| responses | HAL and collections, or templates and redirects; errors. | +| openapi | swagger-php attributes, for apps that publish OpenAPI. | +| feature-docs | Feature docs: template, where they live, staleness. | +| testing | Unit and functional patterns, test config, coverage matrix. | +| evolution-pattern | Sunset headers instead of versioning. | +| security | Auth, authorization, secrets, CORS, dependencies. | +| dot-maker | `composer make …` and the manual steps after it. | +| core-submodule | Core layering rules and git submodule mechanics. | +| psr-standards | PSR-1/3/4/6/7/11/12/15/16/17 as applied here. | +| qa-gate | cs-check, static analysis, tests, and forbidden "fixes". | +| troubleshooting | A symptom to cause table. | +| dependency-policy | The ladder from installed packages to hand-rolled code, and the proposal format. | + +A review subagent, `dotkernel-reviewer`, runs the convention review in its own context so the main one stays clean. + +## Never name a package from memory + +The ladder is: already in `composer.lock`, then `dotkernel/*`, then `laminas/*` and `mezzio/*`, then a vetted community package, and only then hand-rolled code. +Stop at the first hit. + +A skill's description decides whether it *can* load, not whether Claude stops to think before naming a package. +The rule that makes it reach for the skill has to be always loaded - which means it lives in your project's own `CLAUDE.md`, not in the skill. + +- [The block to paste](https://github.com/dotkernel/dotboost#add-this-to-your-projects-claudemd) + +### Three things about how that behaves + +- The `dotkernel/*` manifest is generated on first use, not shipped - the first package question runs the sync script itself. It needs `curl`, `jq` and network; without them Claude is told to call a package name **unverified** rather than assert it. +- Neither that script nor `composer show --available` is in the allow or deny list, so both prompt. That is deliberate - expect a prompt the first time. +- "How do I send mail from here?" is a package question. Implicit ones count. + +## Copy one directory into your project + +The payload has to sit at the root of your project. +Two routes to the same files - pick whichever suits the machine you are on. + +### 1 · Clone it inside the project + +Shallow, because only the current state of `main` is any use in a target project. +The clone goes away right after the copy. + +```shell +git clone --depth 1 \ + https://github.com/dotkernel/dotboost.git .dotboost +cp -r .dotboost/.claude .claude +rm -rf .dotboost +``` + +### 1b · Or download the zip + +No git needed, which is the point. +The archive unpacks to `dotboost-main/` and does carry the dotfiles. + +```shell +curl -L -o dotboost.zip https://github.com/\ +dotkernel/dotboost/archive/refs/heads/main.zip +unzip -q dotboost.zip +cp -r dotboost-main/.claude .claude +``` + +### 2 · Restore the executable bits + +Nothing is committed executable, and a zip extracted on Windows carries no permission bits at all. +The sync script is the one called by its own name. + +```shell +chmod +x .claude/hooks/*.sh .claude/statusline.sh \ + .claude/skills/dependency-policy/scripts/*.sh +``` + +### 3 · Keep local overrides out of git + +Then copy the example file if you want personal settings that survive an update. + +```shell +echo '.claude/settings.local.json' \ + >> .git/info/exclude +``` + +### 4 · Optional: markdown linting + +Not bundled - install it yourself, globally or as a project devDependency. +Until the binary resolves, the hook exits silently. + +```shell +npm install -g markdownlint-cli2 +``` + +### 5 · Paste the CLAUDE.md block + +The dependency policy and, if you want feature docs actually read, the feature-docs block alongside it. + +If the project already has its own `.claude/settings.json` or `.claude/commands/`, merge by hand rather than running `cp -r` blind - same-named files are overwritten. +To update later, run the whole install again over the top; anything you changed in the project's copy is lost, which is exactly why personal changes belong in `settings.local.json`. + +## The parts you notice on day two + +### Feature documentation — Docs · /dk-document + +The part a cleared session cannot reconstruct from `src/`. + +One markdown file per feature - what it does, why, the routes and the roles that reach them, the data added, how to exercise it. +Where it lands is detected, not assumed: `documentation/features/` when that directory exists, `docs/features/` otherwise, never a second documentation root beside an existing one. +The frontmatter is load-bearing - `/dk-review` greps `routes:` and `handlers:` to decide whether a new route in the diff is documented. + +### Real usage, not guesses — Status line + +The same numbers `/usage` reports. + +5h and 7d account usage in the status bar, read from the rate-limit payload Claude Code 2.1+ passes on stdin. +Pure bash and awk - no `jq`, no python, no transcript scanning or guessed token budgets, so it works in Git Bash on Windows. + +### A briefing, every time — Session start + +What it detected, before you ask anything. + +Which variant, the root namespace, the authorization style, the branch, which config files are still missing - and a CRLF warning when it finds one. +The same hook prints the feature-docs directory and a doc count, so the files are discoverable even without the CLAUDE.md block. + +### CRLF, handled — Windows · Line endings + +Dotkernel repos ship `* text eol=lf`. + +Belt and braces, configure the client so nothing converts: `core.autocrlf false`, `core.eol lf`, `core.safecrlf warn`. +Already committed CRLF? Renormalise once with `git add --renormalize .`. +`normalize-file.sh` keeps new writes clean. + +## Every variant, one payload + +The application-variants skill detects which Dotkernel application it is looking at - including a project derived from one - and every other skill applies the matching dialect. +Handler naming, response shape and authorization keys all differ between them, which is the whole reason the detection runs first. + +### Detected variants + +`API` `Admin` `Frontend` `Light` `Queue` + +Before adapting the skills to a new Dotkernel application, the advice from its maintainer is to spend an hour reading that repo and correcting them against what is actually there. +A skill written from framework docs rather than the codebase produces confident wrong answers, which is worse than no skill. + +### API — Variant · Detected + +Handlers, HAL collections, OpenAPI attributes. + +A REST API on a PSR-15 middleware pipeline, with OAuth 2.0, RBAC, HAL payloads and an OpenAPI 3.0 specification wired up on install. + +- [Read more](https://www.dotkernel.com/api/) +- [GitHub](https://github.com/dotkernel/api) + +### Admin — Variant · Detected + +Forms, CSRF, route-name authorization keys. + +Table-based record management with RBAC guards, CSRF-protected forms and 2FA, over the shared Core module. + +- [Read more](https://www.dotkernel.com/admin/) +- [GitHub](https://github.com/dotkernel/admin) + +### Frontend — Variant · Detected + +Action controllers and action-level guards. + +A web starter skeleton - user accounts, a contact form, sessions and RBAC-guarded controller actions, rendered on the server. + +- [Read more](https://www.dotkernel.com/frontend/) +- [GitHub](https://github.com/dotkernel/frontend) + +### Light — Variant · Detected + +Minimal modules, config-declared template routes. + +The smallest complete Mezzio application - routing, pipeline and Twig, six direct dependencies and no database layer. + +- [Read more](https://www.dotkernel.com/light/) +- [GitHub](https://github.com/dotkernel/light) + +### Queue — Variant · Detected + +Message handlers and background workers. + +Background workers on Symfony Messenger - a TCP listener, Valkey streams, retries and a dead letter queue for what still fails. + +- [Read more](https://www.dotkernel.com/queue/) +- [GitHub](https://github.com/dotkernel/queue) + +## Open source, in production + +Context your AI tools do not have to guess at. + +Dotboost is maintained by Borsan Sergiu and released as open source alongside the rest of the Dotkernel ecosystem. +Treat the skills as living documents - the repository is where corrections belong. + +[Read the full README →](https://github.com/dotkernel/dotboost) diff --git a/public/md-pages/frontend.md b/public/md-pages/frontend.md new file mode 100644 index 00000000..ed6a2fd9 --- /dev/null +++ b/public/md-pages/frontend.md @@ -0,0 +1,283 @@ +--- +title: "Dotkernel Frontend | Server-rendered PHP web starter" +description: "Dotkernel Frontend is a web starter skeleton on Mezzio and Laminas - user accounts, a contact form, RBAC guards, CSRF-protected forms, flash messages and GDPR account anonymization, server-rendered in Twig." +canonical_url: "https://www.dotkernel.com/frontend/" +language: "en" +--- + +# Dotkernel Frontend + +Web starter · Server-rendered + +A web starter skeleton on the Mezzio microframework and Laminas components, for the applications people log into. +User accounts, a working contact form and a content page ship as proof of concept - real, running features whose only job is to show you where your own code goes. + +- [Read the docs](https://docs.dotkernel.org/frontend/) +- [View on GitHub](https://github.com/dotkernel/frontend) +- [Live demo](https://v5.dotkernel.net/) + +| | | +| --- | --- | +| Runtime | Mezzio + Laminas | +| Pattern | Action controllers | +| Templating | Twig | + +## Request lifecycle + +Session (`dot-session`) → Router (FastRoute) → Authentication (User identity) → RBAC guard (route + action) → Controller action (`dot-controller`) → Response (Twig + flash). + +## The web application half of the stack + +Frontend is the skeleton for a server-rendered application with users in it. +Where Light gives you pages, Frontend gives you the machinery around them: sessions, authentication, registration, validated forms, flash messages between redirects, and role-based access to individual controller actions. + +The shipped features - contact us, a generic content page, user accounts - are deliberately presented as building blocks rather than a finished product. +They exist to showcase the file architecture and to be copied. + +Extending the power of Mezzio by Laminas. + +- User accounts, from register to unregister +- Action controllers, not request handlers +- CSRF and reCAPTCHA on public forms +- GDPR anonymization out of the box + +## Everything a logged-in site needs first + +These are the parts you would otherwise spend your first two weeks assembling, already working together in a fresh install. + +### Registration & login — Users · Accounts + +The whole account lifecycle, already routed. + +Login, registration and account management, including avatar upload, password change and unregistering. +Password reset and account activation emails are part of the flow, which is why the skeleton stores a name and an email address and nothing more. + +### Guards per action — Security · Access control + +Permissions applied to individual controller actions. + +`dot-rbac-guard` and `dot-rbac` read roles and permissions from `authorization.global.php`, then `authorization-guards.global.php` maps rules onto a route and a named list of its actions - or an empty list to cover all of them. +Fine-grained without being scattered. + +### CSRF tokens — Security · Forms + +A new token per render, validated on submit. + +Built from the `laminas-form` CSRF element, a `laminas-session` CSRF validator in the input filter, and the `formElement` view helper in the template. +Tokens expire after a configurable timeout - one hour by default - and are never reusable between forms. + +### Contact form with reCAPTCHA — Public · Contact + +A public form that does not become a spam relay. + +The contact form uses Google reCAPTCHA, with the site and secret keys read from local configuration and the message recipients - `to` and any number of `cc` addresses - configured alongside them. +Whitelist `localhost` while developing, and take it out again for production. + +### Flash messages — UX · Feedback + +The message survives the redirect. + +`dot-flashmessenger` carries session messages across redirects - the small piece that makes post-then-redirect flows feel finished. +`dot-session` extends laminas-session underneath, configured in `session.global.php`. + +### Attribute injection — Wiring · DI + +Constructor injection declared on the constructor. + +`dot-dependency-injection` reads an `#[Inject]` attribute and resolves each listed dependency - a service, the whole `config` array, or a single key by dot notation. +Register the class against `AttributedServiceFactory` and stop writing factories. + +### Doctrine ORM — Data · Persistence + +Migrations and fixtures, driven from the CLI. + +Doctrine through `roave/psr-container-doctrine`, with UUIDs as a field type via `ramsey/uuid-doctrine`. +Migrations live in `data/doctrine/migrations`; `bin/doctrine fixtures:execute` seeds the default roles. + +### Headers & CORS — Delivery · HTTP + +Response headers declared per route. + +`dot-response-header` sets custom headers per route from `response-header.global.php`, while `mezzio-cors` handles origins, headers and cookies from `cors.global.php`. + +### Menus, templates, i18n — Content · Presentation + +Navigation from configuration, translation when you need it. + +`dot-navigation` defines and parses menus inside templates from configuration. +`dot-twigrenderer` adds Dotkernel's Twig extensions, and `laminas-i18n` is present for a complete translation suite. +The `Plugin` module carries dynamic forms and templates. + +## Account anonymization, not just deletion + +Under the GDPR, a company recording personal data from EU citizens must delete it on request - or anonymize it, which the European Commission accepts as an alternative. +Frontend implements the second option, because deleting a user row is rarely what your foreign keys want. + +The skeleton stores only what it needs to run those flows: first name, last name and the email address used as the identity, for password reset and account activation. +Anonymizing replaces exactly those. + +- [Anonymization reference](https://docs.dotkernel.org/frontend/v5/reference/account-anonymization/) + +### What anonymization changes + +- First and last name become `anonymous` plus the current UNIX timestamp - for example `anonymous1725980747`. +- The email becomes the same value plus whatever you set in `userAnonymizeAppend` - `anonymous1725980747@example.com`. +- The avatar image and its database record are deleted. + +Point `userAnonymizeAppend` at a domain you control and it doubles as a catch-all address, if your mail provider supports one. +Leave it empty and the local part stands alone. + +## Five modules, PSR-4 throughout + +Each module keeps its controllers, entities, repositories and services together, alongside a `ConfigProvider` and a `RoutesDelegator` - so a new feature has an obvious shape before you write it. + +### `User` + +Login, registration and account management - the largest module, and the one worth reading first. + +### `App` + +Core functionality: authentication, rendering and error reporting. + +### `Contact` + +The contact us form, from validation through to the outgoing mail. + +### `Page` + +Displaying a page - the minimal case, for static copy. + +### `Plugin` + +Plugin functionality for dynamic forms and templates. + +### Module contents + +`Controller`, `Entity`, `Repository` and `Service` folders, plus `InputFilter`, `EventListener`, `Helper`, `Command` or `Factory` as needed. + +## From clone to welcome page + +The documentation walks through every command with its expected output. +This is the sequence. + +### 1 · Clone into an empty folder + +Git refuses a non-empty directory, and you need write permissions on it. + +```shell +git clone https://github.com/dotkernel/frontend.git . +``` + +### 2 · Install dependencies + +From the CLI, so the prompts stay interactive. +Decline the config provider injection - Frontend ships its own. + +```shell +composer install +``` + +### 3 · Enable development mode + +Sets debug on, configuration caching off, and clears any existing cache. + +```shell +composer development-enable +``` + +### 4 · Prepare the config files + +Copy the `.dist` files into place - `local.php`, `development.local.php`, `mail.local.php`, `debugbar.local.php` - then fill in the database, SMTP and reCAPTCHA details. + +### 5 · Migrate and seed + +Migrations build the schema and are logged so none runs twice; the fixtures populate the default user roles. + +```shell +php vendor/bin/doctrine-migrations migrate +php bin/doctrine fixtures:execute +``` + +### 6 · Fix permissions and open it + +Three writable paths cover almost every first-run error. + +```shell +chmod -R 777 data log public/uploads +``` + +Two local-only notes worth keeping: `session.cookie_secure` has to be `false` in your own `local.php` - never in `local.php.dist`, where it stays `true` for production - and a stale `data/cache/config-cache.php` is loaded regardless of `ConfigAggregator::ENABLE_CACHE`, so clear it with `bin/clear-config-cache.php` when services go missing. +Duplicating `local.test.php.dist` gives your tests an in-memory database. + +## What the server needs + +| Component | Requirement | +| --- | --- | +| Operating system | A \*nix based system is strongly recommended for production. | +| PHP | 8.2 or newer, mod_php or FCGI (FPM). `memory_limit` at least 128M; `upload_max_filesize` and `post_max_size` at least 100M depending on your data. | +| Web server | Apache 2.2+ with `mod_rewrite` and `.htaccess` support (`AllowOverride All`); a default `.htaccess` ships in `public/`. On Nginx, translate it into server configuration. | +| Database | Tested with MariaDB 10.11 LTS and 11.4 LTS, and with MySQL 8.4 LTS. For MySQL 8.4, `my.cnf` needs `mysql_native_password=ON`. | +| Required extensions | `mbstring`, the CLI SAPI for cron jobs, and Composer on `$PATH`. | +| Recommended extensions | `opcache`; `pdo_mysql` or `mysqli`; `dom` and `simplexml` for markup; `gd` and `exif` for images; `zlib`, `zip`, `bz2` for compression; `curl` when calling APIs; `sqlite3` for the test suite. | + +Note that Frontend still supports MySQL - unlike API and Admin v7, which require native UUID support and therefore PostgreSQL or MariaDB 10.7+. + +## Where Frontend sits + +Frontend stands on its own, outside the Headless Platform. +It is the right starting point when your users log in and your pages are rendered on the server - and the wrong one in both directions from there. + +### Light — Smaller · Minimal + +No users, no database, no forms to protect. + +The smallest complete Mezzio application - routing, pipeline and Twig, six direct dependencies. +Right for a presentation site. + +- [Read more](https://www.dotkernel.com/light/) +- [Demo](https://light.dotkernel.net/) + +### API — Different · HTTP surface + +Your frontend is someone else's framework. + +A REST API on a PSR-15 middleware pipeline, with OAuth 2.0, RBAC, HAL payloads and an OpenAPI 3.0 specification wired up on install. + +- [Read more](https://www.dotkernel.com/api/) +- [Demo](https://api.dotkernel.net/) + +### Admin — Bigger · Platform + +You need a back office over a shared domain. + +Table-based record management with RBAC guards, CSRF-protected forms and 2FA, over a Core module shared with API and Queue. + +- [Read more](https://www.dotkernel.com/admin/) +- [Demo](https://admin7.dotkernel.net/) + +### Queue — Alongside · Async work + +Move the registration email off the request. + +Background workers on Symfony Messenger - a TCP listener, Valkey streams, retries and a dead letter queue for what still fails. + +- [Read more](https://www.dotkernel.com/queue/) +- [GitHub](https://github.com/dotkernel/queue) + +### Dotboost — Tooling · AI context + +Teach your AI tools this architecture. + +Drop-in Claude Code configuration - ten commands, seventeen skills and permission guardrails that keep your secrets out of the context window. + +- [Read more](https://www.dotkernel.com/dotboost/) +- [GitHub](https://github.com/dotkernel/dotboost) + +## Open source, in production + +Accounts, forms and compliance - already handled. + +Dotkernel Frontend is developed and led by the dev team at Apidemia and released as open source for the community. +Try the demo to see the account flows before you install anything. + +[Talk to us →](https://www.dotkernel.com/contact/) diff --git a/public/md-pages/light.md b/public/md-pages/light.md new file mode 100644 index 00000000..a0fb4454 --- /dev/null +++ b/public/md-pages/light.md @@ -0,0 +1,299 @@ +--- +title: "Dotkernel Light | Minimal PSR-15 site skeleton for PHP" +description: "Dotkernel Light is a minimal PSR-15 application skeleton on Mezzio and Laminas, for presentation sites and small services. Six direct dependencies, Twig templating, Vite assets, no database layer." +canonical_url: "https://www.dotkernel.com/light/" +language: "en" +--- + +# Dotkernel Light + +Minimal skeleton · PSR-15 + +A PSR-15 compliant application skeleton on the Mezzio microframework and Laminas components, designed as a minimal project for generating a simple website - a presentation site, a landing page, a small service. +Six direct dependencies, two modules, and nothing to delete before you start. + +- [Read the docs](https://docs.dotkernel.org/light-documentation/) +- [View on GitHub](https://github.com/dotkernel/light) +- [Live demo](https://light.dotkernel.net/) + +| | | +| --- | --- | +| Runtime | Mezzio + Laminas | +| Templating | Twig | +| Direct deps | Six | + +## Request lifecycle + +`index.php` (bootstrap) → Container (factories · aliases) → Routing (FastRoute) → Pipeline (`pipeline.php`) → Handler (PSR-15) → Twig (layout · blocks) → Emitter (HtmlResponse). + +## The smallest complete Mezzio application + +Light is what remains when you take a real PSR-15 application and remove everything a presentation site does not need. +There is no ORM, no authentication stack, no admin scaffolding - not disabled, simply not installed. +What you get is routing, a middleware pipeline, Twig, and error logging. + +That makes it two things at once: a genuine starting point for a small site, and the clearest way to read how a Mezzio application is put together, because there is almost nothing else in the way. + +Extending the power of Mezzio by Laminas. + +- Six direct dependencies +- No database layer to configure +- A new page is one config line and one template +- Twig layout, Vite asset pipeline + +## Six packages, and you can name all of them + +This is the entire direct dependency list. +Every one of them is doing a job you would have had to solve anyway. + +### [mezzio/mezzio](https://github.com/mezzio/mezzio) + +The PSR-15 middleware microframework the whole application runs on. + +### [mezzio-fastroute](https://github.com/mezzio/mezzio-fastroute) + +FastRoute integration - matches the URL and method against your registered routes. + +### [mezzio-twigrenderer](https://github.com/mezzio/mezzio-twigrenderer) + +Twig integration for Mezzio. +Every template is a `.html.twig` file. + +### [dot-errorhandler](https://github.com/dotkernel/dot-errorhandler) + +Logging error handler for middleware applications - daily log files, configured in one place. + +### [laminas-config-aggregator](https://github.com/laminas/laminas-config-aggregator) + +Collects and merges configuration from every source into one array. + +### [laminas-component-installer](https://github.com/laminas/laminas-component-installer) + +The Composer plugin that injects modules and config providers during installation. + +Two modules ship in `src/`: `App` for core functionality - rendering and error reporting - and `Page` for displaying a page. +Each module keeps its `Handler`, `Factory` and `Service` folders next to a `ConfigProvider` and a `RoutesDelegator`, following PSR-4. + +## A request, end to end + +Eleven steps from the browser to the rendered page, with nothing hidden behind a kernel. +This is the sequence you can follow in the source. + +| # | Step | What happens | +| --- | --- | --- | +| 1 | HTTP request | `public/index.php` bootstraps the application, loads configuration, and creates the Mezzio application instance. | +| 2 | Service container | Factories, aliases and delegators are registered; every service is configured and ready to use. | +| 3 | Route registration | All available routes and their allowed methods are read and registered dynamically, managed by FastRoute. | +| 4 | Middleware pipeline | `config/pipeline.php` defines the order middleware runs in, and so how requests travel and responses come back. | +| 5 | Routing | FastRoute matches the URL and method against the registered routes to find the handler. | +| 6 | Handler invocation | The matched route name is pulled off the request and passed to the renderer - `page::about` becomes the template to render. | +| 7 | Custom logic | Your business logic runs in the handler, calling whatever services it needs. | +| 8 | Template rendering | Twig loads the template, applies the layout, renders the blocks and includes the partials. | +| 9 | Response creation | An `HtmlResponse` is built with status, headers and the rendered HTML body. | +| 10 | Response pipeline | The response flows back out through the middleware stack, which can still change headers, cookies or compression. | +| 11 | Response emitter | The final response is sent to the browser. | + +Handlers, not controllers - that is what keeps the application PSR-15 compliant. +Each module's `RoutesDelegator` reads the route config from the container and registers the routes it finds, so adding a page never means touching the framework. + +## Adding a page is two files + +Routes live in configuration as `slug => template` pairs. +Append a line, create the matching Twig template in `src/Page/templates/page/`, and add a link to the menu in the layout. +The handler works out which template to render from the matched route name, so there is no handler to write. + +Want your templates in more than one folder? +Add another entry under `paths` in the Page module's `ConfigProvider` - the key does not have to match the folder name. + +- [Creating pages](https://docs.dotkernel.org/light-documentation/v1/how-tos/create-pages/) + +### One route, one template + +In `config/autoload/local.php`, under the `routes` → `page` key: + +```php +'example-page' => 'example-template', +``` + +Then create the template it names: + +```text +src/Page/templates/page/example-template.html.twig +``` + +The key is the page slug, the value is the template. +Your new page answers at `/page/example-page`, under the route name `page::example-template`. + +## Assets, menus and social cards + +The parts you actually edit on a presentation site, and where each of them lives. + +### Asset pipeline — Build · Vite + +One command while you work, one before you ship. + +Vite concatenates and compresses CSS and JavaScript, preprocesses SCSS, and copies fonts and images - avoiding the network bottleneck of many separate files. +`npm run watch` recompiles on change; `npm run build` compiles once. +Node.js v20 is the minimum supported version. + +### assets → public — Build · Source of truth + +Edit the source, never the output. + +Images, fonts, JavaScript and SCSS live in `src/App/assets/`. +The build deletes and rebuilds `css`, `js`, `fonts` and `images` under `public/` - anything you edit there by hand is lost on the next run. +Everything else in `public/` is left alone. + +### Cache busting — Build · Caching + +The oldest bug in web deployment, solved in one character. + +Browsers cache your built CSS and JS, so a deploy can leave visitors on the old file. +Add a version parameter to the asset URL in the layout - `app.css?v=3` - and increment it whenever you commit a change to that file. + +### Menu and footer — Content · Navigation + +Plain Twig in one layout file. + +The top menu is the list under `id="navbarHeader"` in `src/App/templates/layout/default.html.twig`; each `li` is one item, and items can be grouped into dropdowns or styled as buttons. +The footer is the `app-footer` element in the same file. + +### Twitter & OpenGraph — Content · Sharing + +Cards for when your pages get posted elsewhere. + +Add the card meta tags to the layout head, generating URLs with the same helper the canonical block uses - `{{ url(routeName ?? null) }}` - which also keeps mistyped URLs from breaking the page. + +### Errors and logs — Operations · Logs + +A log file per day, from the first request. + +`dot-errorhandler` writes daily files into `log/`, in the format set under the `stream` key of `error-handling.global.php`. +Development mode adds the error handlers you want locally and nowhere else. + +## Running in minutes, honestly + +No database to create, no fixtures to seed. +Clone, install, set a URL, open it. + +### 1 · Clone into an empty folder + +Git refuses a directory that is not empty, and you need write permissions on it. + +```shell +git clone https://github.com/dotkernel/light.git . +``` + +### 2 · Install dependencies + +Run it from the CLI so the prompts stay interactive. +Decline the config provider injection - Light already includes its own. + +```shell +composer install +``` + +### 3 · Enable development mode + +Local work only. +`composer development-status` reports where you stand. + +```shell +composer development-enable +``` + +### 4 · Set the base URL + +Point `$baseUrl` in `config/autoload/local.php` at your virtual host. + +### 5 · Fix the writable folders + +The two directories the application writes to. +Most first-run errors are this and nothing else. + +```shell +chmod -R 777 ./data ./log +``` + +### 6 · Open it in a browser + +The Dotkernel Light welcome page is waiting. +Errors about missing services usually mean a stale config cache. + +```shell +php ./bin/clear-config-cache.php +``` + +A cached `data/cache/config-cache.php` is loaded regardless of the `ConfigAggregator::ENABLE_CACHE` setting - which is exactly why clearing it fixes so much. +On Windows, WSL2 with AlmaLinux is the recommended development environment. + +## What the server needs + +| Component | Requirement | +| --- | --- | +| Operating system | A \*nix based system is strongly recommended for production. | +| PHP | 8.2, 8.3 or 8.4, with mod_php or FCGI (FPM). `memory_limit` at least 128M. | +| Web server | Apache 2.2+ with `mod_rewrite` and `.htaccess` support (`AllowOverride All`); a default `.htaccess` ships in `public/`. On Nginx, translate it into server configuration. | +| Database | None. Light has no persistence layer - which is the point. | +| Required extensions | `mbstring`, plus Composer available on `$PATH`. | +| Recommended extensions | `opcache`; `dom` and `simplexml` for markup; `gd` and `exif` for images; `zlib`, `zip`, `bz2` for compression; `curl` if you call APIs. | + +## When Light is the wrong answer + +Light sits outside the Headless Platform on purpose. +It has no sessions, no forms, no authentication and no database - so the moment you need those, start from a skeleton that already has them rather than growing them here. + +### Frontend — Instead · Full-stack web + +You need logins, forms and sessions. + +A server-rendered web starter on Mezzio and Laminas - Twig views, forms, sessions and RBAC-guarded routes, still standing on its own outside the platform. + +- [Read more](https://www.dotkernel.com/frontend/) +- [GitHub](https://github.com/dotkernel/frontend) +- [Demo](https://v5.dotkernel.net/) + +### API — Instead · HTTP surface + +You are serving clients, not pages. + +A REST API on a PSR-15 middleware pipeline, with OAuth 2.0, RBAC, HAL payloads and an OpenAPI 3.0 specification wired up on install. + +- [Read more](https://www.dotkernel.com/api/) +- [GitHub](https://github.com/dotkernel/api) + +### Admin — Instead · Back office + +You need to manage records, not publish copy. + +Table-based record management with RBAC guards, CSRF-protected forms and 2FA, over a Core module shared with the rest of the platform. + +- [Read more](https://www.dotkernel.com/admin/) +- [GitHub](https://github.com/dotkernel/admin) + +### Queue — Instead · Async work + +You have work that outlives a request. + +Background workers on Symfony Messenger - a TCP listener, Valkey streams, retries and a dead letter queue for what still fails. + +- [Read more](https://www.dotkernel.com/queue/) +- [GitHub](https://github.com/dotkernel/queue) + +### dotboost — Tooling · AI context + +Teach your AI tools this architecture. + +Drop-in Claude Code configuration - ten commands, seventeen skills and permission guardrails that keep your secrets out of the context window. + +- [Read more](https://www.dotkernel.com/dotboost/) +- [GitHub](https://github.com/dotkernel/dotboost) + +## Open source, in production + +Small enough to read, real enough to ship. + +Dotkernel Light is developed and led by the dev team at Apidemia, and released as open source for the community. +If you want to see the whole application before you commit to it, that is the entire promise of this skeleton. + +[Talk to us →](https://www.dotkernel.com/contact/) diff --git a/public/md-pages/queue.md b/public/md-pages/queue.md new file mode 100644 index 00000000..9a5670fd --- /dev/null +++ b/public/md-pages/queue.md @@ -0,0 +1,294 @@ +--- +title: "Dotkernel Queue | Asynchronous task queue for PHP" +description: "Dotkernel Queue runs time-consuming work on background workers instead of inside the request cycle. A Swoole TCP daemon, Valkey streams and Symfony Messenger, with retries, a dead letter queue and reporting commands." +canonical_url: "https://www.dotkernel.com/queue/" +language: "en" +--- + +# Dotkernel Queue + +Background workers · Symfony Messenger + +Some operations are time-consuming and resource-intensive, and they have no business inside a request. +Dotkernel Queue accepts them over a TCP connection, stores them, and runs them on background workers - so your platform answers the next request instead of waiting for a video to finish encoding. + +- [Read the docs](https://docs.dotkernel.org/queue-documentation/) +- [View on GitHub](https://github.com/dotkernel/queue) + +| | | +| --- | --- | +| Listener | Swoole on TCP | +| Store | Valkey streams | +| Worker | Symfony Messenger | + +## Message lifecycle + +Your application (JSON payload) → TCP :8556 (IP whitelisted) → Swoole daemon (accepts & stores) → messages stream (Valkey · FIFO) → Messenger worker (your handler) → failed stream (after 3 retries). + +## A separate machine for the slow work + +Dotkernel Queue is built on **Symfony Messenger**, integrated into Mezzio and Laminas applications through the `netglue/laminas-messenger` adapter for the Laminas Service Manager container. +Work runs decoupled from the regular request-response cycle, on background workers that can live on their own hardware. + +The main platform returns its response and stays responsive to new requests. +Tasks with long execution times are scheduled to run when resources are available, oldest first. + +Extending the power of Mezzio by Laminas. + +- High request rate without overloading +- FIFO - the oldest message runs first +- Retries with exponential backoff +- Failures parked, never blocking the queue + +## Work that should never block a response + +Tasks suited to the queue take extended periods to execute and may be interrupted by PHP limits such as `max_execution_time`; depend on external systems that add authentication delays, slow replies, or fail outright when a server is offline; or simply are not part of the PHP response at all. + +### Data processing + +Big data analytics, scientific simulations, mathematical computations - anything where the data size or the algorithm sets the clock. + +### File & media processing + +Video and image processing, compressing and decompressing large files. + +### Networking + +Uploading and downloading large files, where bandwidth rather than your code is the limit. + +### Database operations + +Imports, exports and migrations that would otherwise hold a web request open. + +### System & infrastructure + +OS updates, software compilation, CI pipelines triggered from your application. + +### Transactional email + +The classic case: hand the message to the queue and let the worker compose and send it while the server moves to the next task. + +[Sending emails →](https://docs.dotkernel.org/queue-documentation/v2/how-to/send-emails/) + +## Two daemons, one stream + +A listener that accepts messages fast, and a worker that processes them carefully. +They are separate systemd services, so you can restart either one without losing the other. + +### The TCP listener — Ingest · Swoole + +Accepts connections and gets out of the way. + +An active daemon listens for TCP connections on a port - 8556 by default - and stores incoming messages immediately. +This supports a large number of requests per second without overloading, because accepting a message is all it does. + +### The messages stream — Store · Valkey + +An open-source key/value datastore built for this shape of work. + +Messages land in the `messages` stream, the main queue. +The worker consumes from it in FIFO order - the oldest request first, then newer ones - and processes each according to your application logic. +Valkey is BSD-licensed and handles caching, message queues, and primary datastore workloads. + +### The worker — Process · Messenger + +Your handler, running outside the request cycle. + +A Messenger worker consumes the stream and hands each message to your handler. +Because it is a separate service on a separate machine if you want one, a slow job costs you worker time rather than user-facing latency. + +### Dead letter queue — Resilience · DLQ + +Failures get parked, not retried forever. + +Each transport defines a retry strategy. +When a message exceeds its allowed retries it is forwarded automatically to the failure transport and stored in the `failed` stream - so a message that cannot succeed never blocks the ones behind it. + +### Whitelisted senders — Security · Firewall + +The port is open to your servers, not to the internet. + +The documented setup adds a firewall rich rule that accepts traffic on the queue's port only from the source addresses you name. +Fast to evaluate, and a small attack surface for a service that takes instructions. + +### Metrics you can query — Observability · Logs + +Logging that answers the operational questions. + +Processed and failed messages are logged, which is what makes the reporting commands possible: queue length, processing time per job, error rates, and throughput in jobs per second. + +## Backoff, configured in one place + +The transport's `retry_strategy` decides how hard the worker tries before a message is handed to the dead letter queue. +These are the shipped defaults. + +| Setting | Default | What it controls | +| --- | --- | --- | +| `max_retries` | 3 | Attempts before the message moves to the failure transport. | +| `delay` | 1000 | Initial wait before retrying a failed message, in milliseconds. | +| `multiplier` | 2 | Each retry's delay is multiplied by this factor - exponential backoff. | +| `max_delay` | 0 | Ceiling on the wait between retries; 0 means unlimited or default behaviour. | +| `failure_transport` | `failed` | Where messages go once they exceed the retry limit. | + +Both transports are Redis-protocol DSNs pointing at a stream - `messages` for new work, `failed` for what could not be processed - with the serializer configurable per transport. + +## Console commands, over CLI or TCP + +The same three commands answer from the shell on the queue server or from a socket on another machine. +Every flag is optional; when both `--start` and `--end` are given, `--limit` is ignored. + +| Command | Returns | Invocation | +| --- | --- | --- | +| `failed` | Log entries for messages that failed to process. | `php bin/cli.php failed --start="yyyy-mm-dd" --end="yyyy-mm-dd" --limit=int` | +| `processed` | Log entries for messages processed successfully. | `php bin/cli.php processed --start="yyyy-mm-dd" --end="yyyy-mm-dd" --limit=int` | +| `inventory` | Everything currently queued in the `messages` stream. | `php bin/cli.php inventory` | +| `control` | A test message, logged as processed - the quickest end-to-end check. | `echo "control" \| socat -t1 - TCP:host:port` | + +Valkey itself stays inspectable: `valkey-cli` gives you `PING`, `INFO`, `KEYS *`, and stream commands such as `XRANGE streamName - +` to read entries oldest to newest, or `XTRIM streamName MAXLEN 0` to empty a stream while keeping the key. + +## Two ways to hand over a message + +The **procedural** approach opens a TCP connection, writes the JSON payload, and closes the socket. +It is simple and quick to implement - and harder to reuse as the project grows. +Make sure the message ends with a newline, or the server will keep waiting for the rest of it. + +The **object-oriented** approach wraps the queue in a service - a small `NotificationSystem` module under Core, built on `clue/socket-raw`, with its connection details injected from configuration. +Handlers then inject the service and call a method that describes the intent, not the transport. + +- [Integration guide](https://docs.dotkernel.org/queue-documentation/v2/how-to/communication-with-queue/) + +### Smoke-test the listener + +From your local machine, with the daemon running and your IP whitelisted: + +```shell +echo "Hello" | socat -T1 - TCP:SERVER-IP:8556 +``` + +The `-T1` timeout is optional but wise: without it, a server that never replies leaves `socat` waiting indefinitely. + +## A queue server, from a bare box + +The documented path is AlmaLinux 9 or 10 with root access, adapted accordingly for other operating systems. +Everything runs as a non-root user. + +### 1 · Prepare the server + +Update the OS, create a user with sudo permissions, and install the utilities you will need - including `socat` for testing. + +```shell +dnf update -y && useradd dotkernel +``` + +### 2 · Install the runtime + +PHP from the Remi repository, plus the Swoole and Redis PECL extensions. +Verify both are loaded before moving on. + +```shell +dnf module enable php:remi-8.5 +dnf install php-pecl-swoole6 php-pecl-redis +``` + +### 3 · Install Valkey + +Enable and start the service, then confirm it answers. + +```shell +dnf install valkey +valkey-cli ping +``` + +### 4 · Clone and configure + +Clone the queue branch, then copy each `.dist` configuration file into place - local, log, messenger and swoole - and fill them in. + +```shell +git clone -b default-queue https://github.com/dotkernel/queue.git +composer install --no-dev +``` + +### 5 · Register the daemons + +Set the paths in the shipped unit files, copy them into `/etc/systemd/system/`, then enable and start both. + +```shell +systemctl enable --now swoole.service +systemctl enable --now messenger.service +``` + +### 6 · Close the door + +Allow SSH before starting the firewall, then permit the queue port only from the addresses that should reach it. + +```shell +firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="YOUR_IP" port port="8556" protocol="tcp" accept' +``` + +Full commands, expected output and the reasoning behind each step are in the [server setup](https://docs.dotkernel.org/queue-documentation/v2/server-setup/) and [installation](https://docs.dotkernel.org/queue-documentation/v2/installation/) guides. + +## The third deployable + +Queue processes work produced by the rest of the platform. +Share the `Core` module with it - copied in or added as a submodule - and your worker has the same entities, services and mail configuration as the application that queued the job. + +### Shared domain layer + +`Core\Admin` `Core\App` `Core\Security` `Core\Setting` `Core\User` + +Keep Core in sync between the main project and the queue and every class, service and configuration needed for message processing is already there - which is what makes a worker able to compose an email from your own templates. + +### API — Pair with · HTTP surface + +Dispatch work from an endpoint, answer immediately. + +A REST API on a PSR-15 middleware pipeline, with OAuth 2.0, RBAC, HAL payloads and an OpenAPI 3.0 specification wired up on install. + +- [Read more](https://www.dotkernel.com/api/) +- [GitHub](https://github.com/dotkernel/api) + +### Admin — Pair with · Back office + +Queue a bulk operation from an admin screen. + +Table-based record management with RBAC guards, CSRF-protected forms and 2FA, over the same Core module. + +- [Read more](https://www.dotkernel.com/admin/) +- [GitHub](https://github.com/dotkernel/admin) + +### Frontend — Pair with · Public-facing + +Queue the email your signup form triggers. + +A web starter skeleton - user accounts, a contact form, sessions and RBAC-guarded controller actions, rendered on the server. + +- [Read more](https://www.dotkernel.com/frontend/) +- [Demo](https://v5.dotkernel.net/) + +### Light — Smaller · Minimal + +A site with nothing to run in the background. + +The smallest complete Mezzio application - routing, pipeline and Twig, six direct dependencies and no database layer. + +- [Read more](https://www.dotkernel.com/light/) +- [Demo](https://light.dotkernel.net/) + +### Dotboost — Tooling · AI context + +Teach your AI tools this architecture. + +Drop-in Claude Code configuration - ten commands, seventeen skills and permission guardrails that keep your secrets out of the context window. + +- [Read more](https://www.dotkernel.com/dotboost/) +- [GitHub](https://github.com/dotkernel/dotboost) + +The queue's own dependencies are the same small `dot-*` components the rest of the platform uses - each publishing its own support status on the [packages lifecycle](https://www.dotkernel.com/dotkernel-packages-oss-lifecycle/) page. + +## Open source, in production + +Let the slow work happen somewhere else. + +Dotkernel Queue is developed and led by the dev team at Apidemia - built to keep real platforms responsive under real load, and released as open source for the community. + +[Talk to us →](https://www.dotkernel.com/contact/) diff --git a/public/md-pages/who-we-are.md b/public/md-pages/who-we-are.md new file mode 100644 index 00000000..19343c18 --- /dev/null +++ b/public/md-pages/who-we-are.md @@ -0,0 +1,27 @@ +--- +title: "Who We Are" +canonical_url: "https://www.dotkernel.com/who-we-are/" +language: "en" +--- + +# demo page + +WHO WE ARE + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. +Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + +## [heading](https://docs.mezzio.dev/mezzio/) + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. +Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + +## [heading](https://github.com/laminas/laminas-diactoros) + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. +Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + +## [heading](https://github.com/laminas/laminas-stratigility) + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. +Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. diff --git a/src/App/assets/scss/components/_custom.scss b/src/App/assets/scss/components/_custom.scss index d8b00c55..fc6d6db2 100644 --- a/src/App/assets/scss/components/_custom.scss +++ b/src/App/assets/scss/components/_custom.scss @@ -284,6 +284,34 @@ header { } } +// Products dropdown +.navbar-nav .dropdown-menu { + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 6px; + min-width: 190px; + + .dropdown-item { + font-size: 14.5px; + color: var(--text-muted); + border-radius: calc(var(--radius) / 2); + padding: 8px 12px; + transition: background .15s ease, color .15s ease; + + &:hover, + &:focus { + background: var(--surface-2); + color: var(--text); + } + } +} + +.navbar-nav .dropdown-toggle::after { + margin-left: .4em; + vertical-align: .15em; +} + .nav-cta { font-family: var(--mono); font-size: 13.5px; @@ -342,6 +370,26 @@ html[data-theme="dark"] .navbar-toggler-icon { font-size: 16px; } + // collapsed navbar renders the dropdown inline, not as a floating card + .dropdown-menu { + background: transparent; + border: 0; + border-bottom: 1px solid var(--border-soft); + border-radius: 0; + padding: 0 0 6px 14px; + margin: 0; + + .dropdown-item { + padding: 11px 0; + font-size: 15px; + + &:hover, + &:focus { + background: transparent; + } + } + } + .nav-cta { margin-top: 16px; text-align: center; @@ -485,6 +533,15 @@ html[data-theme="light"] .theme-toggle { flex-wrap: wrap; } +// The markdown actions bar (@partial/markdown-actions.html.twig) hugs the right edge on blog +// articles. Under the hero CTAs, or under the intro copy on a demo page, it lines up with them. +.hero .md-actions-bar, +.page-intro .md-actions-bar { + justify-content: flex-start; + margin-top: 16px; + margin-bottom: 0; +} + .hero-stack-note { margin-top: 34px; font-family: var(--mono); @@ -514,6 +571,18 @@ html[data-theme="light"] .theme-toggle { gap: 18px; } +// The hero artwork sits on its own full-width row under the hero grid, so the figure spans the +// content width rather than the narrow right-hand column. Its SVG is drawn on a 1180-wide canvas. +.hero-art-row { + margin-top: 46px; +} + +@media (max-width: 980px) { + .hero-art-row { + margin-top: 36px; + } +} + .hero-art { position: relative; background: var(--surface); @@ -760,7 +829,7 @@ html[data-theme="light"] .theme-toggle { border: 1px solid var(--border); border-radius: 12px; padding: 22px 18px; - text-align: left; + text-align: center; .icon { margin-bottom: 12px; @@ -1218,6 +1287,53 @@ footer { padding: 50px 0 40px; } +// Link columns mirroring the top menu, above the copyright/social row. +.footer-nav { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 30px; + padding-bottom: 34px; + margin-bottom: 34px; + border-bottom: 1px solid var(--border-soft); + + ul { + list-style: none; + margin: 0; + padding: 0; + } + + li + li { + margin-top: 10px; + } + + a { + font-size: 13.5px; + color: var(--text-muted); + text-decoration: none; + transition: color .15s ease; + + &:hover { + color: var(--text); + } + } +} + +.footer-col-title { + font-family: var(--mono); + font-size: 11px; + letter-spacing: .12em; + text-transform: uppercase; + color: var(--text-dim); + margin: 0 0 14px; +} + +@media (max-width: 700px) { + .footer-nav { + grid-template-columns: 1fr; + gap: 26px; + } +} + .footer-top { display: flex; justify-content: space-between; @@ -1377,6 +1493,7 @@ footer { .icon-lg { width: 50px; height: 50px; + margin: auto; } .icon-jetbrains { diff --git a/src/App/src/Factory/FeedGeneratorFactory.php b/src/App/src/Factory/FeedGeneratorFactory.php index 154a5e58..58e16b9b 100644 --- a/src/App/src/Factory/FeedGeneratorFactory.php +++ b/src/App/src/Factory/FeedGeneratorFactory.php @@ -22,7 +22,7 @@ public function __invoke(ContainerInterface $container): FeedGenerator return new FeedGenerator( $postRepository, $config['feed']['path'], - $config['application']['baseUrl'] ?? '', + $config['application']['url'] ?? '', $config['application']['meta']['title'] ?? '', $config['application']['meta']['description'] ?? '', $config['application']['meta']['image'] ?? '', diff --git a/src/App/src/Factory/LlmsFullGeneratorFactory.php b/src/App/src/Factory/LlmsFullGeneratorFactory.php index 81faf8d3..971e8f90 100644 --- a/src/App/src/Factory/LlmsFullGeneratorFactory.php +++ b/src/App/src/Factory/LlmsFullGeneratorFactory.php @@ -16,7 +16,9 @@ public function __invoke(ContainerInterface $container): LlmsFullGenerator return new LlmsFullGenerator( $config['llms']['sourceDir'], $config['llms']['outputFile'], - $config['application']['baseUrl'] ?? '', + $config['application']['url'] ?? '', + // Optional: a config predating the static-page markdown simply skips those sections. + $config['llms']['pagesDir'] ?? null, ); } } diff --git a/src/App/src/Factory/SitemapGeneratorFactory.php b/src/App/src/Factory/SitemapGeneratorFactory.php index 099afd34..726df32b 100644 --- a/src/App/src/Factory/SitemapGeneratorFactory.php +++ b/src/App/src/Factory/SitemapGeneratorFactory.php @@ -43,7 +43,7 @@ public function __invoke(ContainerInterface $container): SitemapGenerator $authorRepository, $pageRoutes, $config['sitemap']['path'], - $config['application']['baseUrl'] ?? '', + $config['application']['url'] ?? '', ); } } diff --git a/src/App/src/Service/LlmsFullGenerator.php b/src/App/src/Service/LlmsFullGenerator.php index 699bf9a3..cb9389ac 100644 --- a/src/App/src/Service/LlmsFullGenerator.php +++ b/src/App/src/Service/LlmsFullGenerator.php @@ -8,6 +8,7 @@ use function array_map; use function array_merge; +use function basename; use function count; use function file_get_contents; use function file_put_contents; @@ -24,15 +25,21 @@ * Concatenates every article under the markdown source directory into a single * plain-text file for LLM consumption, in the same format as llms.txt/llms-full.txt * conventions (index.md first, then every other article sorted by relative path). + * + * When a pages directory is configured, the flat `*.md` files in it - the markdown + * versions of the static pages - are appended after the articles, labelled with a + * `md-pages/` prefix so their origin stays readable in the output. */ readonly class LlmsFullGenerator { - private const SEPARATOR = ''; + private const SEPARATOR = ''; + private const PAGES_ROOT = 'md-pages'; public function __construct( private string $sourceDir, private string $outputFile, private string $baseUrl, + private ?string $pagesDir = null, ) { } @@ -43,11 +50,11 @@ public function getOutputFile(): string public function write(): int { - $relativePaths = $this->collectRelativePaths(); + $entries = $this->collectEntries(); $sections = array_map( - fn (string $relativePath): string => $this->buildSection($relativePath), - $relativePaths + fn (array $entry): string => $this->buildSection($entry['file'], $entry['label']), + $entries ); $written = implode("\n\n\n", $sections) . "\n\n"; @@ -56,13 +63,15 @@ public function write(): int throw new RuntimeException('Unable to write llms-full.txt.'); } - return count($relativePaths); + return count($entries); } /** - * @return list + * Absolute file path plus the label written into each section header. + * + * @return list */ - private function collectRelativePaths(): array + private function collectEntries(): array { $categoryFiles = glob($this->sourceDir . '/*/*.md') ?: []; $categoryFiles = array_map( @@ -71,14 +80,46 @@ private function collectRelativePaths(): array ); sort($categoryFiles); - $paths = is_file($this->sourceDir . '/index.md') ? ['index.md'] : []; + $labels = is_file($this->sourceDir . '/index.md') ? ['index.md'] : []; + $labels = array_merge($labels, $categoryFiles); - return array_merge($paths, $categoryFiles); + $entries = array_map( + fn (string $label): array => [ + 'file' => $this->sourceDir . '/' . $label, + 'label' => $label, + ], + $labels + ); + + return array_merge($entries, $this->collectPageEntries()); + } + + /** + * The flat `*.md` files in the pages directory, appended after the articles. + * + * @return list + */ + private function collectPageEntries(): array + { + if ($this->pagesDir === null) { + return []; + } + + $pageFiles = glob($this->pagesDir . '/*.md') ?: []; + sort($pageFiles); + + return array_map( + fn (string $path): array => [ + 'file' => $path, + 'label' => self::PAGES_ROOT . '/' . basename($path), + ], + $pageFiles + ); } - private function buildSection(string $relativePath): string + private function buildSection(string $file, string $relativePath): string { - $contents = file_get_contents($this->sourceDir . '/' . $relativePath); + $contents = file_get_contents($file); if ($contents === false) { throw new RuntimeException("Unable to read file: {$relativePath}"); } diff --git a/src/App/templates/JSON-LD/index.jsonld.twig b/src/App/templates/JSON-LD/index.jsonld.twig index b1db64ae..7572cd4a 100644 --- a/src/App/templates/JSON-LD/index.jsonld.twig +++ b/src/App/templates/JSON-LD/index.jsonld.twig @@ -1,12 +1,12 @@ -{% set organizationId = app.baseUrl ~ '/#organization' %} +{% set organizationId = app.url ~ '/#organization' %} diff --git a/src/Blog/templates/page/JSON-LD/dotkernel-api/api-client-migration-from-postman-to-bruno.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel-api/api-client-migration-from-postman-to-bruno.jsonld.twig index e8803833..ab71fce6 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel-api/api-client-migration-from-postman-to-bruno.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel-api/api-client-migration-from-postman-to-bruno.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", diff --git a/src/Blog/templates/page/JSON-LD/dotkernel-api/api-endpoint-to-collect-client-errors.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel-api/api-endpoint-to-collect-client-errors.jsonld.twig index d428b7f9..c3d46c1c 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel-api/api-endpoint-to-collect-client-errors.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel-api/api-endpoint-to-collect-client-errors.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", diff --git a/src/Blog/templates/page/JSON-LD/dotkernel-api/content-negotiation-in-dotkernel-rest-api.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel-api/content-negotiation-in-dotkernel-rest-api.jsonld.twig index a9eb1472..109c05d1 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel-api/content-negotiation-in-dotkernel-rest-api.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel-api/content-negotiation-in-dotkernel-rest-api.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", diff --git a/src/Blog/templates/page/JSON-LD/dotkernel-api/dotkernel-api-1-0-0-released.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel-api/dotkernel-api-1-0-0-released.jsonld.twig index fcc00e37..e6fd5ece 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel-api/dotkernel-api-1-0-0-released.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel-api/dotkernel-api-1-0-0-released.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", diff --git a/src/Blog/templates/page/JSON-LD/dotkernel-api/dotkernel-api-client-side-authorization.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel-api/dotkernel-api-client-side-authorization.jsonld.twig index 061a6204..707213b5 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel-api/dotkernel-api-client-side-authorization.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel-api/dotkernel-api-client-side-authorization.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", diff --git a/src/Blog/templates/page/JSON-LD/dotkernel-api/dotkernel-api-server-side-authorization.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel-api/dotkernel-api-server-side-authorization.jsonld.twig index bf434921..8826651b 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel-api/dotkernel-api-server-side-authorization.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel-api/dotkernel-api-server-side-authorization.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", diff --git a/src/Blog/templates/page/JSON-LD/dotkernel-api/dotkernel-api-versus-laminas-api-tools.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel-api/dotkernel-api-versus-laminas-api-tools.jsonld.twig index e38c13fe..e87ea3ab 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel-api/dotkernel-api-versus-laminas-api-tools.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel-api/dotkernel-api-versus-laminas-api-tools.jsonld.twig @@ -2,8 +2,8 @@ { "@context": "https://schema.org", "@type": "BlogPosting", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "headline": "{{ article.title }}", "description": "{{ article.excerpt }}", "datePublished": "{{ article.postDate|date('c') }}", @@ -12,15 +12,15 @@ "author": { "@type": "Person", "name": "{{ article.author.name }}", - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } } diff --git a/src/Blog/templates/page/JSON-LD/dotkernel-api/error-reporting-endpoint-in-dotkernel-api.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel-api/error-reporting-endpoint-in-dotkernel-api.jsonld.twig index e38c13fe..e87ea3ab 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel-api/error-reporting-endpoint-in-dotkernel-api.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel-api/error-reporting-endpoint-in-dotkernel-api.jsonld.twig @@ -2,8 +2,8 @@ { "@context": "https://schema.org", "@type": "BlogPosting", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "headline": "{{ article.title }}", "description": "{{ article.excerpt }}", "datePublished": "{{ article.postDate|date('c') }}", @@ -12,15 +12,15 @@ "author": { "@type": "Person", "name": "{{ article.author.name }}", - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } } diff --git a/src/Blog/templates/page/JSON-LD/dotkernel-api/how-to-implement-mailchimp-in-dotkernel-api.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel-api/how-to-implement-mailchimp-in-dotkernel-api.jsonld.twig index 731bf129..a1d15cab 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel-api/how-to-implement-mailchimp-in-dotkernel-api.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel-api/how-to-implement-mailchimp-in-dotkernel-api.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "Which library does this tutorial use to add MailChimp to Dotkernel API?", "acceptedAnswer": { "@type": "Answer", "text": "The tutorial uses drewm/mailchimp-api, installed with the command composer require drewm/mailchimp-api." } }, { "@type": "Question", "name": "Where do you place the MailChimp configuration file?", "acceptedAnswer": { "@type": "Answer", "text": "In config/autoload/mailchimp.global.php, a new configuration file created as part of Step 2." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel-api/openapi-implementation-in-dotkernel-api.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel-api/openapi-implementation-in-dotkernel-api.jsonld.twig index a1a6afd1..9fc489f9 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel-api/openapi-implementation-in-dotkernel-api.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel-api/openapi-implementation-in-dotkernel-api.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What is the OpenAPI Specification?", "acceptedAnswer": { "@type": "Answer", "text": "A consistent way to develop and interact with an API. It defines API structure and syntax in a universal way, regardless of the programming language used, typically described in YAML or JSON so users can quickly discover endpoints, request/response formats, security mechanisms and more." } }, { "@type": "Question", "name": "How does OpenAPI compare to Postman?", "acceptedAnswer": { "@type": "Answer", "text": "OpenAPI standardizes how endpoints and request/response models are described, automatically generates machine-readable documentation, lets you define the API specification before development starts, and improves collaboration across teams. Postman, by contrast, is used mainly to test an already-completed endpoint and has no real focus on standardized documentation or upfront design." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel-packages-oss-lifecycle.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel-packages-oss-lifecycle.jsonld.twig index 7edbcae9..ff10690b 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel-packages-oss-lifecycle.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel-packages-oss-lifecycle.jsonld.twig @@ -2,9 +2,9 @@ { "@context": "https://schema.org", "@type": "WebPage", - "@id": "{{ app.baseUrl ~ path('page::dotkernel-packages-oss-lifecycle') }}#webpage", - "url": "{{ app.baseUrl ~ path('page::dotkernel-packages-oss-lifecycle') }}", + "@id": "{{ app.url ~ path('page::dotkernel-packages-oss-lifecycle') }}#webpage", + "url": "{{ app.url ~ path('page::dotkernel-packages-oss-lifecycle') }}", "name": "Dotkernel Packages OSS Lifecycle", - "isPartOf": { "@id": "{{ app.baseUrl }}#website" } + "isPartOf": { "@id": "{{ app.url }}#website" } } diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/adding-a-cors-implementation-to-zend-expressive.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/adding-a-cors-implementation-to-zend-expressive.jsonld.twig index 888975a9..504917b4 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/adding-a-cors-implementation-to-zend-expressive.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/adding-a-cors-implementation-to-zend-expressive.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What causes the \"No 'Access-Control-Allow-Origin' header\" error?", "acceptedAnswer": { "@type": "Answer", "text": "It means the server didn't send the header that lets a local client, such as a browser, access its data. This is most common when trying to fetch data (usually JSON) that you want to process using JavaScript." } }, { "@type": "Question", "name": "What package does the article use to add CORS support?", "acceptedAnswer": { "@type": "Answer", "text": "Tuupola's Cors Middleware package, installed by running composer require tuupola/cors-middleware in the project." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/adding-a-second-caching-layer-to-wurfl-in-dotkernel-using-apc.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/adding-a-second-caching-layer-to-wurfl-in-dotkernel-using-apc.jsonld.twig index 01c36989..9fa5ce9a 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/adding-a-second-caching-layer-to-wurfl-in-dotkernel-using-apc.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/adding-a-second-caching-layer-to-wurfl-in-dotkernel-using-apc.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "Why was WURFL's default caching too slow for this project?", "acceptedAnswer": { "@type": "Answer", "text": "Profiling revealed that WURFL's default filesystem cache was taking up to a few hundred milliseconds of response time, which was too slow for a relatively high-traffic application." } }, { "@type": "Question", "name": "How does WURFL's caching work by default?", "acceptedAnswer": { "@type": "Answer", "text": "Device data is stored in a large zipped XML file. On first use, WURFL unzips the file, serializes each device's data, and writes it to cache using an MD5 signature of the user agent as the key. Because devices are stored as a tree inheriting properties from parent nodes, each lookup requires reading and merging several files." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/adding-composer-support-in-your-dotkernel-project.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/adding-composer-support-in-your-dotkernel-project.jsonld.twig index 0d58343d..a108a9d5 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/adding-composer-support-in-your-dotkernel-project.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/adding-composer-support-in-your-dotkernel-project.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What does Composer do?", "acceptedAnswer": { "@type": "Answer", "text": "Composer is an application-level package manager. It auto-loads dependencies on demand and can also auto-load custom classes." } }, { "@type": "Question", "name": "What must a Dotkernel project have before Composer can be used?", "acceptedAnswer": { "@type": "Answer", "text": "A composer.json file, for example requiring zendframework/zendframework1 at 1.12.* and mobiledetect/mobiledetectlib at 2.8.*, plus PHP >=5.4.0 listed under require-dev." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/adding-windows-10-os-and-browser-detection-in-dotkernel-projects.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/adding-windows-10-os-and-browser-detection-in-dotkernel-projects.jsonld.twig index 5fdcce52..90ce277b 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/adding-windows-10-os-and-browser-detection-in-dotkernel-projects.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/adding-windows-10-os-and-browser-detection-in-dotkernel-projects.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What Dotkernel version is required before applying this patch?", "acceptedAnswer": { "@type": "Answer", "text": "Your project must be running version 1.5.0 or newer." } }, { "@type": "Question", "name": "Which files does the patch modify or add?", "acceptedAnswer": { "@type": "Answer", "text": "It modifies configs/useragent/browser.xml and configs/useragent/os.xml, and adds images/browsers/edge.png and images/os/windows_metro.png." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/autologin-using-cookie-remember-me-in-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/autologin-using-cookie-remember-me-in-dotkernel.jsonld.twig index 484a03f1..79ebcb89 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/autologin-using-cookie-remember-me-in-dotkernel.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/autologin-using-cookie-remember-me-in-dotkernel.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "Since which release is Remember Me implemented in Dotkernel?", "acceptedAnswer": { "@type": "Answer", "text": "It's implemented in Dotkernel Frontend starting from Release 3.3.0." } }, { "@type": "Question", "name": "How do you add the Remember Me checkbox to the login UI?", "acceptedAnswer": { "@type": "Answer", "text": "In src/User/templates/user/login.html.twig, under the password element, render the rememberMe element retrieved via form.get('rememberMe') with formElement()." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/avoid-routing-through-bootstrap-of-non-existent-files.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/avoid-routing-through-bootstrap-of-non-existent-files.jsonld.twig index eaf9fd95..fa772f3a 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/avoid-routing-through-bootstrap-of-non-existent-files.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/avoid-routing-through-bootstrap-of-non-existent-files.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What problem does this fix address?", "acceptedAnswer": { "@type": "Answer", "text": "When missing static files (images, CSS, JS) are routed through the bootstrap (index.php) and the session is set to regenerate on each request, the currently logged-in user gets logged off because the session ID changes." } }, { "@type": "Question", "name": "What is the fix?", "acceptedAnswer": { "@type": "Answer", "text": "Below the \"RewriteEngine On\" line, add a RewriteCond matching file extensions like .gif, .jpg, .png, .css, and .js, so requests for those files are not routed through the bootstrap." } } diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/caching-in-dotkernel-using-zend-framework.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/caching-in-dotkernel-using-zend-framework.jsonld.twig index 5c211075..ff0c3333 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/caching-in-dotkernel-using-zend-framework.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/caching-in-dotkernel-using-zend-framework.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What data does Dotkernel's cache layer store?", "acceptedAnswer": { "@type": "Answer", "text": "Router, acl_role, menu, options (including seo_xml), browser_xml, os_xml, and test data between requests." } }, { "@type": "Question", "name": "What storage providers are available for the cache?", "acceptedAnswer": { "@type": "Answer", "text": "Two cache factories to choose from: APC (or APCU for newer PHP installations) and File." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/camelcase-table-names-in-mysql-on-windows.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/camelcase-table-names-in-mysql-on-windows.jsonld.twig index f0e8a4ad..26d42fe7 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/camelcase-table-names-in-mysql-on-windows.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/camelcase-table-names-in-mysql-on-windows.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What happens when you create a camelCase table name on WAMP or XAMPP?", "acceptedAnswer": { "@type": "Answer", "text": "A table created with a camelCase name, for example adminLogin, ends up stored as all lowercase, e.g. adminlogin, instead." } }, { "@type": "Question", "name": "How do you fix it?", "acceptedAnswer": { "@type": "Answer", "text": "Add the line lower_case_table_names=2 to your my.cnf file and restart MySQL." } } diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/commitment-to-php-new-zend-certified-engineers-zce-in-our-team.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/commitment-to-php-new-zend-certified-engineers-zce-in-our-team.jsonld.twig index fe220e0d..c19602aa 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/commitment-to-php-new-zend-certified-engineers-zce-in-our-team.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/commitment-to-php-new-zend-certified-engineers-zce-in-our-team.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "How many Zend Certified Engineers does the team have?", "acceptedAnswer": { "@type": "Answer", "text": "According to the article, 2 more team members passed the ZCE exam, bringing the team's total to 5 Zend Certified Engineers." } } ] diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/configuring-the-cache-in-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/configuring-the-cache-in-dotkernel.jsonld.twig index 54e37c7a..f962e1ff 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/configuring-the-cache-in-dotkernel.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/configuring-the-cache-in-dotkernel.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What is Dotkernel's caching layer based on?", "acceptedAnswer": { "@type": "Answer", "text": "It's based on Zend Framework Cache, configured through settings in application.ini, with more configuration options available at the Zend Framework Cache Frontends and Backends documentation links given in the article." } }, { "@type": "Question", "name": "What does the cache.enable setting do?", "acceptedAnswer": { "@type": "Answer", "text": "It can be used to disable caching, which is mostly useful during the development stage." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dependency-injection-made-easy-in-laminas-mezzio-applications.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dependency-injection-made-easy-in-laminas-mezzio-applications.jsonld.twig index 289fe680..16fdb988 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dependency-injection-made-easy-in-laminas-mezzio-applications.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/dependency-injection-made-easy-in-laminas-mezzio-applications.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What problem does dot-dependency-injection solve?", "acceptedAnswer": { "@type": "Answer", "text": "In Laminas/Mezzio applications, developers normally have to build, update, and maintain a factory class for every class that needs dependencies. dot-dependency-injection autowires those dependencies instead, so you don't need a factory for every class." } }, { "@type": "Question", "name": "Does dot-dependency-injection require Doctrine ORM?", "acceptedAnswer": { "@type": "Answer", "text": "The package requires Doctrine ORM, but the article notes it can still be used in applications that don't integrate Doctrine." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/detecting-mobile-devices-in-dotkernel-1-6-0.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/detecting-mobile-devices-in-dotkernel-1-6-0.jsonld.twig index 9a45f9c6..90f1d5b4 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/detecting-mobile-devices-in-dotkernel-1-6-0.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/detecting-mobile-devices-in-dotkernel-1-6-0.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "Why did mobile detection change in Dotkernel 1.6.0?", "acceptedAnswer": { "@type": "Answer", "text": "Because of the new Wurfl Cloud integration. This version no longer ships with a working built-in method for mobile detection, so it must be configured first." } }, { "@type": "Question", "name": "What are the steps to configure Wurfl Cloud detection?", "acceptedAnswer": { "@type": "Answer", "text": "Go to the scientiamobile website and register for a Wurfl Cloud account, choose the device_os and mobile_browser capabilities for the account and save, then go to API Keys and copy the key into application.ini." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/disable-wurfl-redirect-for-mobile-browsers.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/disable-wurfl-redirect-for-mobile-browsers.jsonld.twig index 79ce99c0..44b09bb1 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/disable-wurfl-redirect-for-mobile-browsers.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/disable-wurfl-redirect-for-mobile-browsers.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "How do you disable the automatic mobile redirect in Dotkernel?", "acceptedAnswer": { "@type": "Answer", "text": "Starting with revision 408, set resources.useragent.wurflapi.redirect = false in application.ini. Per the article, this is also the default state of the redirect option." } }, { "@type": "Question", "name": "Where in the code is this configuration option checked?", "acceptedAnswer": { "@type": "Answer", "text": "In Controllers/frontend/IndexController.php (around line 19), a condition checks whether the redirect is enabled in application.ini, whether the visiting browser is mobile, and whether session->mobileHit isn't already set, before registering and redirecting the visit." } } diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/disambiguation-dotkernel-1-and-dotkernel-3.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/disambiguation-dotkernel-1-and-dotkernel-3.jsonld.twig index 9768d043..11f11168 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/disambiguation-dotkernel-1-and-dotkernel-3.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/disambiguation-dotkernel-1-and-dotkernel-3.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What does the name \"Dotkernel\" mean?", "acceptedAnswer": { "@type": "Answer", "text": "It combines \"Dot\", as a representation of the Internet, with \"Kernel\", the quintessence of any IT application, reflecting the aim of being a central part of Internet development." } }, { "@type": "Question", "name": "What is Dotkernel 1?", "acceptedAnswer": { "@type": "Answer", "text": "A PHP Application Framework built on top of Zend Framework 1, first publicly released in July 2010, with an architecture based on MVC. Its latest version is 1.8 Long Term Support, which per the article will not be followed by a new version, only bugfixes." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/doctrine-cache-using-symfony-cache.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/doctrine-cache-using-symfony-cache.jsonld.twig index 1d0c280b..6fe45383 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/doctrine-cache-using-symfony-cache.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/doctrine-cache-using-symfony-cache.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What component does this article use for caching Doctrine data?", "acceptedAnswer": { "@type": "Answer", "text": "The dotkernel/dot-cache component, a wrapper that sits on top of symfony/cache. It's installed with composer require dotkernel/dot-cache and registered by adding DotCacheConfigProvider::class to the configuration aggregate." } }, { "@type": "Question", "name": "What storage adapters does dot-cache currently support?", "acceptedAnswer": { "@type": "Answer", "text": "Two: array, which stores data in-memory and is the fastest option but uses more RAM, and filesystem, which stores data in local disk files and is slightly slower but more persistent." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/doctrine-enum-implementation-in-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/doctrine-enum-implementation-in-dotkernel.jsonld.twig index 0227ae44..50bd15ed 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/doctrine-enum-implementation-in-dotkernel.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/doctrine-enum-implementation-in-dotkernel.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What update triggered this change to Dotkernel's enum handling?", "acceptedAnswer": { "@type": "Answer", "text": "The update of doctrine/orm to version 3.2.0 introduced EnumType columns, building on the enum type introduced in PHP 8.1. Dotkernel implemented this new data type on both the PHP side and the database side." } }, { "@type": "Question", "name": "What was the limitation of Dotkernel's old approach to columns like User->Status?", "acceptedAnswer": { "@type": "Answer", "text": "The old setup used a simple string type, so the value set couldn't be definitively enforced. A typo in a PHP value would still be accepted, and the database was independent of any values the PHP code allowed, so manually editing a value in the database would accept any string." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotboost-technologies-products-and-services-north-american-relaunch.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotboost-technologies-products-and-services-north-american-relaunch.jsonld.twig index 05f6f8b8..658420cb 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotboost-technologies-products-and-services-north-american-relaunch.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/dotboost-technologies-products-and-services-north-american-relaunch.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What is at the heart of Dotboost's North American relaunch?", "acceptedAnswer": { "@type": "Answer", "text": "The source release of Dotboost's exclusive, in-house developed Dotkernel framework, along with added business IT integration and increased clarity around existing consulting services." } }, { "@type": "Question", "name": "When was Dotboost founded, and at what stages can it join a project?", "acceptedAnswer": { "@type": "Answer", "text": "Dotboost was founded in 2005. Per the article, its process can incorporate anywhere into a project's life-cycle, including concept development, architecture and design, development and integration, and implementation and support." } } diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-2-0-release.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-2-0-release.jsonld.twig index e8be8840..67ecf61d 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-2-0-release.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-2-0-release.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What is a \"dots\" in Dotkernel, a term introduced in this release?", "acceptedAnswer": { "@type": "Answer", "text": "A term for a submodule and all its component files. For example, \"user\" is a dots of the frontend module, and one dots can belong to multiple modules, such as \"user\" belonging to both frontend and admin." } }, { "@type": "Question", "name": "Where are dots configuration values stored, compared to earlier versions?", "acceptedAnswer": { "@type": "Answer", "text": "They're stored in XML files inside the configs/dots folder. In previous versions, these values were hard-coded in the PHP files." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-2-2-release.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-2-2-release.jsonld.twig index bc04d868..a2ef2cc4 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-2-2-release.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-2-2-release.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What does the Dotkernel 1.2.2 release include?", "acceptedAnswer": { "@type": "Answer", "text": "Dotkernel 1.2.2 is a bug-fix release that closes five issues: captcha error handling (try/catch), a pagination issue, a wrong admin link, notices and a fatal error found by an Acunetix scan, and an update to the copyright line in files." } }, { "@type": "Question", "name": "Why did all PHP files change in the 1.2.2 release?", "acceptedAnswer": { "@type": "Answer", "text": "Because of the fix for bug 35, which updated the copyright line, every PHP file in the codebase was touched, which is why the note in the post warns that all PHP files have changed." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-3-0-release.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-3-0-release.jsonld.twig index 8ceb8cc1..026d7600 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-3-0-release.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-3-0-release.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What is new in the admin interface in Dotkernel 1.3.0?", "acceptedAnswer": { "@type": "Answer", "text": "1.3.0 adds a skin switcher for the admin, with several ready-made skins (blue, brown, gray, green) that can be set via the settings.admin.skin configuration value." } }, { "@type": "Question", "name": "How do I protect a page so only logged-in members can access it?", "acceptedAnswer": { "@type": "Answer", "text": "Add the line Dot_Auth::checkIdentity(); in the controller file above the code you want to protect - everything below that line requires the visitor to be logged in." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-3-2-release.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-3-2-release.jsonld.twig index f23e3829..6003d778 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-3-2-release.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-3-2-release.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What kind of release is Dotkernel 1.3.2?", "acceptedAnswer": { "@type": "Answer", "text": "It's mainly a maintenance release, containing many bug fixes, some refactoring, and a few minor features." } }, { "@type": "Question", "name": "What bugs were fixed in 1.3.2?", "acceptedAnswer": { "@type": "Answer", "text": "Fixes include a CSS issue on the admin phpinfo page, a warning in the admin dashboard, a WURFL cache issue and WURFL version issue in admin, a Dot_Paginator bug, a Zend Paginator double-query issue, and a database naming convention issue." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-5-0-released.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-5-0-released.jsonld.twig index f7a81735..4e19dd5e 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-5-0-released.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-5-0-released.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "Why did Dotkernel jump from 1.3 straight to 1.5.0?", "acceptedAnswer": { "@type": "Answer", "text": "Because of the large amount of changes and the long time spent in development, the team chose to skip version 1.4 and go straight to 1.5.0." } }, { "@type": "Question", "name": "Did Dotkernel switch from Dojo to jQuery in 1.5.0?", "acceptedAnswer": { "@type": "Answer", "text": "Yes. Starting with 1.5.0, Dotkernel switched from Dojo to jQuery for its own distribution, though Dojo can still be used in your own projects." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-8-0-lts-released.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-8-0-lts-released.jsonld.twig index cd773a8e..527db6df 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-8-0-lts-released.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-8-0-lts-released.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What does LTS mean for Dotkernel 1.8.0?", "acceptedAnswer": { "@type": "Answer", "text": "LTS stands for Long-term support, a type of special version designed to be supported for longer than normal, which is particularly common for open-source software projects." } }, { "@type": "Question", "name": "What is the Plugin Architecture introduced in 1.8.0?", "acceptedAnswer": { "@type": "Answer", "text": "Starting with 1.8.0, Dotkernel uses Plugins to make extending the framework easier." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-8-1-upgrade-from-1-8-0-released.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-8-1-upgrade-from-1-8-0-released.jsonld.twig index ae899ca5..83a00b9e 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-8-1-upgrade-from-1-8-0-released.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-8-1-upgrade-from-1-8-0-released.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What's new in Dotkernel 1.8.1?", "acceptedAnswer": { "@type": "Answer", "text": "The main change is Enhanced Cache Support, which means you can use tags in your cache system if the host supports it." } }, { "@type": "Question", "name": "How do I upgrade from 1.8.0 to 1.8.1?", "acceptedAnswer": { "@type": "Answer", "text": "The post provides a dedicated \"Upgrade from Dotkernel 1.8.0\" download link, separate from the full Dotkernel 1.8.1 package and the original Dotkernel 1.8.0 (LTS) download." } } diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-coding-standard.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-coding-standard.jsonld.twig index 5b342f46..160d96eb 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-coding-standard.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-coding-standard.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What coding standard does Dotkernel follow?", "acceptedAnswer": { "@type": "Answer", "text": "Dotkernel borrows its coding standard from the Zend Framework Coding Standard, with some exceptions described in this article." } }, { "@type": "Question", "name": "Tabs or spaces for indentation?", "acceptedAnswer": { "@type": "Answer", "text": "Dotkernel indents with tabs, not spaces." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-database-naming-conventions-for-mysql.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-database-naming-conventions-for-mysql.jsonld.twig index 6e359c89..13d82fe1 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-database-naming-conventions-for-mysql.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-database-naming-conventions-for-mysql.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "Where do Dotkernel's database naming conventions come from?", "acceptedAnswer": { "@type": "Answer", "text": "They are borrowed from FaZend's \"Rules of naming of database tables and columns,\" an open-source PHP framework based on Zend Framework." } }, { "@type": "Question", "name": "Should table names be singular or plural?", "acceptedAnswer": { "@type": "Answer", "text": "Singular table names only, for example user, category, product, order, orderProduct." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-light-starting-with-mezzio-microframework-and-laminas-components.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-light-starting-with-mezzio-microframework-and-laminas-components.jsonld.twig index 91686df3..652b30f2 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-light-starting-with-mezzio-microframework-and-laminas-components.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-light-starting-with-mezzio-microframework-and-laminas-components.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What is Dotkernel Light?", "acceptedAnswer": { "@type": "Answer", "text": "Dotkernel Light is the smallest complete Mezzio application that includes only the bare-bones essentials. It's suitable as a presentation site, an introduction to the Mezzio microframework architecture, or a starting point for a more complex project where you want full control over functionality." } }, { "@type": "Question", "name": "What is the goal of Dotkernel Light?", "acceptedAnswer": { "@type": "Answer", "text": "It's designed to be the smallest complete Mezzio application, as well as an entry-level version of Dotkernel Frontend, presenting the beginner developer with as few moving parts as possible while still letting the more advanced developer have full control of the platform's functionality." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-light-the-best-choice-for-your-presentation-site.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-light-the-best-choice-for-your-presentation-site.jsonld.twig index 0758691c..a4ef8587 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-light-the-best-choice-for-your-presentation-site.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-light-the-best-choice-for-your-presentation-site.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What is Dotkernel Light designed for?", "acceptedAnswer": { "@type": "Answer", "text": "It's a good starting point for a project when you want full control over its functionality, and it easily grows into something more complex by integrating packages based on your requirements." } }, { "@type": "Question", "name": "What functionality does Dotkernel Light include out of the box?", "acceptedAnswer": { "@type": "Answer", "text": "Routing, Templating, Error handling, and Tests and code quality checks." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-on-nginx.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-on-nginx.jsonld.twig index 660f01db..000f8762 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-on-nginx.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-on-nginx.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What server stack was used to test Dotkernel on Nginx?", "acceptedAnswer": { "@type": "Answer", "text": "A clean CentOS 6.3 VPS running Nginx 1.4.1, PHP 5.4.16, and PHP-FPM, with the APC and GeoIP PHP modules installed." } }, { "@type": "Question", "name": "What is the Nginx equivalent of the main .htaccess file?", "acceptedAnswer": { "@type": "Answer", "text": "The directive try_files $uri $uri/ /index.php;, which sends all requests to the index.php file." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-reserved-variable-names-for-caching.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-reserved-variable-names-for-caching.jsonld.twig index 458ef9ba..9791e9e9 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-reserved-variable-names-for-caching.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-reserved-variable-names-for-caching.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What cache key is used for the router?", "acceptedAnswer": { "@type": "Answer", "text": "The router, which loads routes settings from router.xml, is cached as router." } }, { "@type": "Question", "name": "What cache key stores the ACL role definitions?", "acceptedAnswer": { "@type": "Answer", "text": "The role.xml file (used by Dot_Auth, from /configs/acl/role.xml, to define user rights) is cached as acl_role." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-template-engine.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-template-engine.jsonld.twig index c307bf49..09e45499 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-template-engine.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-template-engine.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What is the Dotkernel Template Engine based on?", "acceptedAnswer": { "@type": "Answer", "text": "It's an implementation of the PHPLib Template engine for PHP5." } }, { "@type": "Question", "name": "What is the main benefit of using it?", "acceptedAnswer": { "@type": "Answer", "text": "It separates application code from the presentation layer, and separates data manipulation from its final format (HTML, XML, or a formatted e-mail), letting you change a site's look and feel quickly without delving into a lot of PHP variable assignments and print statements." } } diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-version-1-0-in-action.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-version-1-0-in-action.jsonld.twig index 1b00d434..57d74900 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-version-1-0-in-action.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-version-1-0-in-action.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "Who developed Dotkernel and what is it built on?", "acceptedAnswer": { "@type": "Answer", "text": "Dotkernel is DotBoost's in-house developed framework, built on top of Zend Framework. At the time of this article it was at version 1.0, released under the Open Software License (OSL 3.0)." } }, { "@type": "Question", "name": "What architecture does Dotkernel use?", "acceptedAnswer": { "@type": "Answer", "text": "A simplified MVC (Model-View-Controller) architecture that's easy to learn for beginner and intermediate level programmers, achieved by eliminating much of Zend Framework's complexity through a different approach to handling web requests." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/forcing-utf8-connections-and-character-set-in-mysql.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/forcing-utf8-connections-and-character-set-in-mysql.jsonld.twig index 4f578c5f..bfc38ba1 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/forcing-utf8-connections-and-character-set-in-mysql.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/forcing-utf8-connections-and-character-set-in-mysql.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "Why force UTF8 at the MySQL server level instead of relying on each client?", "acceptedAnswer": { "@type": "Answer", "text": "Because you can't control all the scripts that connect to your database (for instance the mysql command line or mysqldump), so forcing the server's collation and character set to UTF8 in my.cnf guarantees it regardless of the connecting client." } }, { "@type": "Question", "name": "What two lines enable UTF8 for all connections in my.cnf?", "acceptedAnswer": { "@type": "Answer", "text": "character_set_server=utf8 and skip-character-set-client-handshake." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/geoip-city-removed-from-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/geoip-city-removed-from-dotkernel.jsonld.twig index ffce11e0..062875d0 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/geoip-city-removed-from-dotkernel.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/geoip-city-removed-from-dotkernel.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "Why was GeoIP City removed from Dotkernel?", "acceptedAnswer": { "@type": "Answer", "text": "The City database on GeoIP extension version 1.1.0 and above was causing a segmentation fault, which resulted in crashing or outputting an error instead of executing the PHP code." } }, { "@type": "Question", "name": "Who is affected by this error?", "acceptedAnswer": { "@type": "Answer", "text": "Users running an older Dotkernel version together with GeoIP extension version 1.1.0 or newer, since the error comes from the extension's incompatibility with the database (dat) file." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/geoip-ip-address-location-in-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/geoip-ip-address-location-in-dotkernel.jsonld.twig index f1f93f37..08cf7f62 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/geoip-ip-address-location-in-dotkernel.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/geoip-ip-address-location-in-dotkernel.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What is GeoIP and what does it let Dotkernel do?", "acceptedAnswer": { "@type": "Answer", "text": "GeoIP is MaxMind's proprietary technology for IP geolocation data. Dotkernel uses it to get user statistics by country, determining a visitor's country, region, city, postal code, or area code in real time." } }, { "@type": "Question", "name": "Where does the GeoIP logic live in Dotkernel?", "acceptedAnswer": { "@type": "Answer", "text": "In library/Dot/Geoip.php, inside the getCountryByIp function." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/handling-and-logging-errors-with-dot-errorhandler-and-dot-log.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/handling-and-logging-errors-with-dot-errorhandler-and-dot-log.jsonld.twig index d69d75cc..46f79986 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/handling-and-logging-errors-with-dot-errorhandler-and-dot-log.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/handling-and-logging-errors-with-dot-errorhandler-and-dot-log.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What two kinds of error handlers does dot-errorhandler provide?", "acceptedAnswer": { "@type": "Answer", "text": "A plain ErrorHandler, which is a copy of Zend Expressive's Zend\\Stratigility\\Middleware\\ErrorHandler (copied because that class is final), and a logging LogErrorHandler, which is the same but with added logging support via the container. Both have factories registered for easier use with the Container." } }, { "@type": "Question", "name": "How do you install dot-errorhandler?", "acceptedAnswer": { "@type": "Answer", "text": "Run composer require dotkernel/dot-errorhandler." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/highcharts-integration-in-dotkernel-1-6-0.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/highcharts-integration-in-dotkernel-1-6-0.jsonld.twig index 589d1318..32cb10b7 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/highcharts-integration-in-dotkernel-1-6-0.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/highcharts-integration-in-dotkernel-1-6-0.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What charting library was integrated in Dotkernel 1.6.0?", "acceptedAnswer": { "@type": "Answer", "text": "Highcharts was integrated in Dotkernel 1.6.0, offering a new, intuitive and interactive charting experience." } }, { "@type": "Question", "name": "What sample charts were added to the admin?", "acceptedAnswer": { "@type": "Answer", "text": "The admin includes samples made with Highcharts: a pie chart (with a small custom feature), a column chart, and a line chart." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/how-to-group-log-files-by-date-using-dot-log.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/how-to-group-log-files-by-date-using-dot-log.jsonld.twig index 6025ea9d..a96e3fee 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/how-to-group-log-files-by-date-using-dot-log.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/how-to-group-log-files-by-date-using-dot-log.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What does dot-log Version 3.1.1 add?", "acceptedAnswer": { "@type": "Answer", "text": "Version 3.1.1 adds the ability to use datetime formatter strings directly in the stream option of your log writer, and it fixes an issue where caching dot-log configs caused logs to be written to the same file instead of being grouped by date." } }, { "@type": "Question", "name": "How do I make sure I'm using the fix in Version 3.1.1?", "acceptedAnswer": { "@type": "Answer", "text": "Make sure you're using the latest version of dot-log by running composer update dotkernel/dot-log." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/how-to-set-a-persistent-connection-to-database-with-zend-framework-zend-db-adapter.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/how-to-set-a-persistent-connection-to-database-with-zend-framework-zend-db-adapter.jsonld.twig index 75697a08..9ea63ef5 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/how-to-set-a-persistent-connection-to-database-with-zend-framework-zend-db-adapter.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/how-to-set-a-persistent-connection-to-database-with-zend-framework-zend-db-adapter.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "Where do you configure a persistent database connection in a Dotkernel project?", "acceptedAnswer": { "@type": "Answer", "text": "The option should be added in application.ini, as shown in the Dotkernel approach used as an example in the article." } }, { "@type": "Question", "name": "What Zend_Db configuration line should NOT be used for a persistent connection?", "acceptedAnswer": { "@type": "Answer", "text": "database.params.options.PDO::ATTR_PERSISTENT = TRUE should not be used - the article states that it will not work." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/how-to-use-alerts-in-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/how-to-use-alerts-in-dotkernel.jsonld.twig index fb7bc9fd..c3c354bd 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/how-to-use-alerts-in-dotkernel.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/how-to-use-alerts-in-dotkernel.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What is a Dot_Alert used for?", "acceptedAnswer": { "@type": "Answer", "text": "Alerts (Dot_Alert's) are e-mails usually sent to the site developers using PHP's mail() function. They shouldn't be used to send regular mail - they only notify the developer that something is wrong." } }, { "@type": "Question", "name": "What does the Dot_Alert class resemble, and what does an alert contain?", "acceptedAnswer": { "@type": "Answer", "text": "Dot_Alert resembles Dot_Email. Like a mail message, an alert has at least a sender, a subject, a destination and a message, and it can be sent." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/implementing-the-new-password-hashing-api-from-php-5-5-in-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/implementing-the-new-password-hashing-api-from-php-5-5-in-dotkernel.jsonld.twig index 43f97f9b..b62eec6e 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/implementing-the-new-password-hashing-api-from-php-5-5-in-dotkernel.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/implementing-the-new-password-hashing-api-from-php-5-5-in-dotkernel.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "Why was Dotkernel refactored to use the new PHP 5.5 Password Hashing API?", "acceptedAnswer": { "@type": "Answer", "text": "To use the new Password Hashing functions introduced in PHP 5.5 and unify all password related functions used for both admin and users, Dotkernel's codebase was refactored in version 1.8.0, starting from revision 799." } }, { "@type": "Question", "name": "How can PHP versions older than 5.5 use these new hashing functions?", "acceptedAnswer": { "@type": "Answer", "text": "Since the 4 new functions are only available since PHP 5.5, Dotkernel used the Password Compatibility library (ircmaxell/password_compat)." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/installing-geoip-extension-in-zend-server-5-6-on-windows.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/installing-geoip-extension-in-zend-server-5-6-on-windows.jsonld.twig index c4b39757..56cd011f 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/installing-geoip-extension-in-zend-server-5-6-on-windows.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/installing-geoip-extension-in-zend-server-5-6-on-windows.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "How do you test if the php_geoip extension is available on Zend Server?", "acceptedAnswer": { "@type": "Answer", "text": "Create a PHP file with var_dump(function_exists('geoip_database_info'));. It outputs true if the extension is available, or false if it isn't." } }, { "@type": "Question", "name": "Where can you download the php_geoip extension for Zend Server 5.6?", "acceptedAnswer": { "@type": "Answer", "text": "From http://windows.php.net/downloads/pecl/releases/geoip/1.0.8/, where several php_geoip.dll files are available for different architectures and compilers." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/installing-geoip-extension-in-zend-server-6-on-windows.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/installing-geoip-extension-in-zend-server-6-on-windows.jsonld.twig index 769b7809..9b42a185 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/installing-geoip-extension-in-zend-server-6-on-windows.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/installing-geoip-extension-in-zend-server-6-on-windows.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "Which php_geoip file should be downloaded for Zend Server 6.1?", "acceptedAnswer": { "@type": "Answer", "text": "Download the php_geoip-1.0.8-5.4-nts-vc9-x86.zip file from http://windows.php.net/downloads/pecl/releases/geoip/1.0.8/." } }, { "@type": "Question", "name": "Where does the php_geoip.dll go on Zend Server 6.1?", "acceptedAnswer": { "@type": "Answer", "text": "Copy the php_geoip.dll file from the archive to ZEND_PATH\\Zend\\ZendServer\\lib\\phpext\\." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/logging-with-dot-log-in-zend-expressive-and-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/logging-with-dot-log-in-zend-expressive-and-dotkernel.jsonld.twig index f0bd8da1..16fb9c19 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/logging-with-dot-log-in-zend-expressive-and-dotkernel.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/logging-with-dot-log-in-zend-expressive-and-dotkernel.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "How do I register dot-log's config provider?", "acceptedAnswer": { "@type": "Answer", "text": "In config/config.php, add \\Dot\\Log\\ConfigProvider::class if it's not already there, making sure it is added before application-specific components such as Frontend\\App\\ConfigProvider or Admin\\App\\ConfigProvider." } }, { "@type": "Question", "name": "How is a logger retrieved from the container instead of using the plain class name?", "acceptedAnswer": { "@type": "Answer", "text": "Dot\\Log\\ConfigProvider registers an abstract factory, LoggerAbstractServiceFactory, that responds to \"selectors\" instead of class names. Instead of requesting Zend\\Log\\Logger::class from the container, you request dot-log.my_logger (or just my_logger if using zend-log)." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/manual-upgrade-of-wurfl-xml-file-in-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/manual-upgrade-of-wurfl-xml-file-in-dotkernel.jsonld.twig index 97dce06d..7afb8453 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/manual-upgrade-of-wurfl-xml-file-in-dotkernel.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/manual-upgrade-of-wurfl-xml-file-in-dotkernel.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "Why does the WURFL xml file need to be manually upgraded in Dotkernel?", "acceptedAnswer": { "@type": "Answer", "text": "Dotkernel bundles an old WURFL XML file (the latest GPL version, from June 2011). Because of a license change to that WURFL file, the bundled file will no longer be upgraded by Dotkernel, so it must be upgraded manually if you still want to use it." } }, { "@type": "Question", "name": "What are the main steps to manually upgrade the WURFL file?", "acceptedAnswer": { "@type": "Answer", "text": "Download the wurfl-2.3.xml.zip file, rename it to wurfl.zip, rename the file inside the archive to wurfl.xml, and replace /externals/wurfl/wurfl.zip with this new wurfl.zip file." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/migration-of-zend-framework-1-pear-channel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/migration-of-zend-framework-1-pear-channel.jsonld.twig index 574f5339..f01e6919 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/migration-of-zend-framework-1-pear-channel.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/migration-of-zend-framework-1-pear-channel.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "Why did the PEAR channel for Zend Framework 1 need to move?", "acceptedAnswer": { "@type": "Answer", "text": "The unofficial PEAR channel for Zend Framework 1 was hosted on Google Code, and once Google Code was closed the channel was forced to move to a new location." } }, { "@type": "Question", "name": "Why wasn't the project moved to GitHub instead?", "acceptedAnswer": { "@type": "Answer", "text": "The team was unable to migrate the project to github.com because the repository is more than 1 GB in size, so they built a special server dedicated to hosting only the PEAR channel for Zend Framework 1." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/new-features-in-zend-framework-1-12.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/new-features-in-zend-framework-1-12.jsonld.twig index 88dfef01..3c0e3b6a 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/new-features-in-zend-framework-1-12.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/new-features-in-zend-framework-1-12.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What was announced for Zend Framework 1.12?", "acceptedAnswer": { "@type": "Answer", "text": "Matthew Weier O'Phinney announced the immediate availability of the first release candidate of the Zend Framework 1.12 series, 1.12.0RC1." } }, { "@type": "Question", "name": "What changed in Zend_Loader?", "acceptedAnswer": { "@type": "Answer", "text": "A number of autoloaders and autoloader facilities were back ported from ZF2, including Zend_Loader_StandardAutoloader, Zend_Loader_ClassMapAutoloader, and Zend_Loader_AutoloaderFactory, providing performant alternatives to the autoloading facilities already in the 1.X releases." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/php-formatter-and-templates-for-zend-studio-10-1.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/php-formatter-and-templates-for-zend-studio-10-1.jsonld.twig index fe3d9623..6bbd6830 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/php-formatter-and-templates-for-zend-studio-10-1.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/php-formatter-and-templates-for-zend-studio-10-1.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "Why does the PHP Formatter plugin for Zend Studio 10.1 need to be changed?", "acceptedAnswer": { "@type": "Answer", "text": "Because Zend donated the Zend Studio Formatter upstream to the PDT project, the Formatter plugin needs to be changed by replacing \"com.zend.php\" with \"org.eclipse.php\"." } }, { "@type": "Question", "name": "Where can I get Dotkernel's updated Formatter and Templates files for Zend Studio 10.1?", "acceptedAnswer": { "@type": "Answer", "text": "The updated PHP Formatter file for Zend Studio 10.1 and a templates file built according to the Dotkernel coding standard are both available as downloads linked in the article." } } diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/protecting-admin-folder-with-htaccess-in-plesk.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/protecting-admin-folder-with-htaccess-in-plesk.jsonld.twig index 4eb8d95c..48c5b218 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/protecting-admin-folder-with-htaccess-in-plesk.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/protecting-admin-folder-with-htaccess-in-plesk.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "How do I protect the /admin folder with .htaccess in Plesk?", "acceptedAnswer": { "@type": "Answer", "text": "Add a Location block for /admin to the vhost.conf file (e.g. /var/www/vhosts/exampledomain.com/conf/vhost.conf) using AuthType Basic, an AuthName, an AuthUserFile pointing to a .htpasswd file, and Require valid-user." } }, { "@type": "Question", "name": "After editing vhost.conf, how do I apply the change and finish setup in Plesk?", "acceptedAnswer": { "@type": "Answer", "text": "Notify the server that vhost.conf has changed by running /usr/local/psa/admin/sbin/websrvmng -a, then create the .htpasswd file and upload it to the same conf directory referenced in the AuthUserFile directive." } } diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/replacing-laminas-mail-with-symfony-mailer-in-dot-mail.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/replacing-laminas-mail-with-symfony-mailer-in-dot-mail.jsonld.twig index 443f0ccc..08bca9fe 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/replacing-laminas-mail-with-symfony-mailer-in-dot-mail.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/replacing-laminas-mail-with-symfony-mailer-in-dot-mail.jsonld.twig @@ -4,45 +4,45 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ { "@type": "ListItem", "position": 1, "name": "Home", - "item": "{{ app.baseUrl }}" + "item": "{{ app.url }}" }, { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, - "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" + "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", @@ -53,7 +53,7 @@ }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/scientia-mobile-licensed-its-wurfl-cloud-php-library-to-dotkernel-1-6.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/scientia-mobile-licensed-its-wurfl-cloud-php-library-to-dotkernel-1-6.jsonld.twig index 54e76310..9a5b3986 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/scientia-mobile-licensed-its-wurfl-cloud-php-library-to-dotkernel-1-6.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/scientia-mobile-licensed-its-wurfl-cloud-php-library-to-dotkernel-1-6.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "Why was the WURFL PHP library removed from Dotkernel 1.6.0?", "acceptedAnswer": { "@type": "Answer", "text": "In Dotkernel 1.6.0, released on May 16th, 2012, the GPL'ed WURFL PHP library was removed because the code was obsolete anyway and the structure of the XML file had changed." } }, { "@type": "Question", "name": "What replaced the old WURFL PHP library?", "acceptedAnswer": { "@type": "Answer", "text": "Dotkernel implemented Scientia Mobile's WURFL Cloud PHP library instead." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/sending-emails-using-dot-email-component-and-zend-email.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/sending-emails-using-dot-email-component-and-zend-email.jsonld.twig index 796349d3..4d78b925 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/sending-emails-using-dot-email-component-and-zend-email.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/sending-emails-using-dot-email-component-and-zend-email.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What is Dot_Email?", "acceptedAnswer": { "@type": "Answer", "text": "Dot_Email is a class that extends Zend_Mail, so all methods from Zend_Mail are available in it. Aside from its constructor, Dot_Email itself is composed of only two methods, with everything else inherited from Zend_Mail." } }, { "@type": "Question", "name": "What are the two methods that Dot_Email adds on top of Zend_Mail?", "acceptedAnswer": { "@type": "Answer", "text": "setContent($content, $format = 'text/plain') sets the body of the email by calling setBodyText() when the format is \"text/plain\" or setBodyHtml() when the format is \"text/html\". send() sets the transporter and calls the parent::send() method to send the email." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/templating-in-dotkernel3.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/templating-in-dotkernel3.jsonld.twig index 23811475..948176e4 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/templating-in-dotkernel3.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/templating-in-dotkernel3.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "Which templating engine does Dotkernel3 use?", "acceptedAnswer": { "@type": "Answer", "text": "Dotkernel3 moved to the popular Twig Templating Engine." } }, { "@type": "Question", "name": "Why did Dotkernel move away from the previous templating engine?", "acceptedAnswer": { "@type": "Answer", "text": "The previous templating engine, although solid and robust, was also 10 years old and used techniques that had become slightly outdated, and it was one of the pain points in the previous version of Dotkernel." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/test-article.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/test-article.jsonld.twig index d10ad556..9ab8095f 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/test-article.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/test-article.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What is this article for?", "acceptedAnswer": { "@type": "Answer", "text": "This is a test article added to the Dotkernel category for fixture and testing purposes." } } ] diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/using-dotkernel-with-composer-dependencies.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/using-dotkernel-with-composer-dependencies.jsonld.twig index 9b832a26..3c506f38 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/using-dotkernel-with-composer-dependencies.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/using-dotkernel-with-composer-dependencies.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "Do I need to manually include or require Composer dependencies in Dotkernel?", "acceptedAnswer": { "@type": "Answer", "text": "No. Composer automatically loads dependencies, so there is no need to include or require them yourself." } }, { "@type": "Question", "name": "What example does the article use to demonstrate Composer dependencies?", "acceptedAnswer": { "@type": "Answer", "text": "The article renders a Barcode using Zend Framework 1 as the Non-Namespaced dependency (installed with composer require 'zendframework/zendframework1') and Zend Framework 2 as the Namespaced dependency (installed with composer require 'zendframework/zend-barcode')." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/using-utf8-charset-in-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/using-utf8-charset-in-dotkernel.jsonld.twig index a47290ec..e5e636b0 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/using-utf8-charset-in-dotkernel.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/using-utf8-charset-in-dotkernel.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What needs to change to use UTF8 encoding in a Dotkernel-based system?", "acceptedAnswer": { "@type": "Answer", "text": "Both the database structure and the application.ini file need changes: all tables and columns must have a proper UTF8 collation (utf8_general_ci or utf8_bin), and application.ini must load the utf8 charset." } }, { "@type": "Question", "name": "What line should I add to application.ini to enable UTF8?", "acceptedAnswer": { "@type": "Answer", "text": "Add the line database.params.charset = utf8 to your application.ini file." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/wurfl-cloud-integration-in-dotkernel-1-6-0.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/wurfl-cloud-integration-in-dotkernel-1-6-0.jsonld.twig index 0acdb070..4c131a63 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/wurfl-cloud-integration-in-dotkernel-1-6-0.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/wurfl-cloud-integration-in-dotkernel-1-6-0.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What is WURFL Cloud?", "acceptedAnswer": { "@type": "Answer", "text": "WURFL Cloud is a new way that WURFL (Wireless Universal Resource FiLe) delivers its device detection services. Dotkernel integrated it as the default detection method for mobile devices starting with version 1.6.0." } }, { "@type": "Question", "name": "How do I get a functional device detection setup with WURFL Cloud?", "acceptedAnswer": { "@type": "Answer", "text": "Register for a Wurfl Cloud account with Scientia Mobile, choose device_os and mobile_browser as your capabilities, then copy your API key into application.ini in Dotkernel." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/wurfl-php-api-license-incompatible-with-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/wurfl-php-api-license-incompatible-with-dotkernel.jsonld.twig index af7fd49c..73da9526 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/wurfl-php-api-license-incompatible-with-dotkernel.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/wurfl-php-api-license-incompatible-with-dotkernel.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "Why did the WURFL PHP API license become incompatible with Dotkernel?", "acceptedAnswer": { "@type": "Answer", "text": "The WURFL PHP API used to be licensed under GNU/GPL, compatible with both Zend Framework's new BSD license and Dotkernel's OSL 3.0 license. On June 6th, 2011, version 1.3.0 of the library changed its license to AGPL, turning it into a trial-only library for product evaluation, which is incompatible with keeping Dotkernel free." } }, { "@type": "Question", "name": "Which version introduced the license change, and when?", "acceptedAnswer": { "@type": "Answer", "text": "Version 1.3.0 of the WURFL PHP API, released June 6th, 2011, changed the license from GNU/GPL to AGPL. Dotkernel had updated to this version in its 1.5.0 release candidate without noticing the license change." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/wurfl-zend-framework-integration-into-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/wurfl-zend-framework-integration-into-dotkernel.jsonld.twig index a9e34eec..bc2e6ced 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/wurfl-zend-framework-integration-into-dotkernel.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/wurfl-zend-framework-integration-into-dotkernel.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "Which Zend Framework class is used to integrate WURFL into Dotkernel?", "acceptedAnswer": { "@type": "Answer", "text": "WURFL is integrated using the Zend_Http_UserAgent class from Zend Framework 1.11.0rc1 (the beta release at the time of the post)." } }, { "@type": "Question", "name": "What is the first step to integrate WURFL into Dotkernel?", "acceptedAnswer": { "@type": "Answer", "text": "Download the WURFL PHP API and unzip it into a folder named wurfl-php-1.1." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/zend-auth-and-zend-acl-integrated-in-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/zend-auth-and-zend-acl-integrated-in-dotkernel.jsonld.twig index 75574969..fdfc3d6f 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/zend-auth-and-zend-acl-integrated-in-dotkernel.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/zend-auth-and-zend-acl-integrated-in-dotkernel.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "When were Zend_Auth and Zend_Acl integrated into Dotkernel?", "acceptedAnswer": { "@type": "Answer", "text": "Zend_Auth and Zend_Acl were integrated starting with Dotkernel version 1.5.0, as part of a refactor of the User and Admin models using the new Dot_Auth and Dot_Acl classes." } }, { "@type": "Question", "name": "How does Dot_Auth authenticate a user?", "acceptedAnswer": { "@type": "Answer", "text": "Dot_Auth authenticates the user by checking the database using Zend_Auth_Adapter_DbTable, setting the table name, the identity column to username, and the credential column to password." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/zend-console-implementation-in-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/zend-console-implementation-in-dotkernel.jsonld.twig index 42460df6..b0fce6a6 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/zend-console-implementation-in-dotkernel.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/zend-console-implementation-in-dotkernel.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What is the purpose of the Console bootstrap in Dotkernel?", "acceptedAnswer": { "@type": "Answer", "text": "Starting with version 1.5, Dotkernel has a Console bootstrap to easily run PHP scripts from the command line, most commonly used to run cron jobs without using wget or going through Apache." } }, { "@type": "Question", "name": "Where is the Console bootstrap located?", "acceptedAnswer": { "@type": "Answer", "text": "The bootstrap is found in Console/index.php, and it has one controller in Controller.php with two example actions." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/zend-framework-dropped-integration-of-wurfl-adapter.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/zend-framework-dropped-integration-of-wurfl-adapter.jsonld.twig index b757021d..04ae7be6 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/zend-framework-dropped-integration-of-wurfl-adapter.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/zend-framework-dropped-integration-of-wurfl-adapter.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "Why did Zend Framework drop the WURFL adapter?", "acceptedAnswer": { "@type": "Answer", "text": "According to Matthew Weier O'Phinney, Zend Framework Project Leader, the WURFL adapter was dropped from Zend_Http_UserAgent in the upcoming 1.12.0 branch due to a change in licensing by the WURFL project: the free version became AGPL, which raised too many concerns for end users regarding how their products must be licensed to comply." } }, { "@type": "Question", "name": "Does this change affect projects built with Dotkernel?", "acceptedAnswer": { "@type": "Answer", "text": "No. Dotkernel uses its own WURFL adapter, so the removal of the WURFL adapter from Zend_Http_UserAgent in ZF 1.12.0 will not affect projects built with Dotkernel." } } diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/zend-registry-usage-in-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/zend-registry-usage-in-dotkernel.jsonld.twig index e058786e..e390c7b0 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/zend-registry-usage-in-dotkernel.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/zend-registry-usage-in-dotkernel.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What variables does Zend_Registry contain in Dotkernel?", "acceptedAnswer": { "@type": "Answer", "text": "It contains startTime (the result of microtime() at the beginning of the request), configuration (loaded from configs/application.ini), router (loaded from configs/router.xml), database (the database adapter), settings (loaded from the database), requestModule/requestController/requestAction, request (additional request variables), seo (loaded from configs/dots/seo.xml), option (loaded from configs/dots/.xml), and session (the session object)." } }, { "@type": "Question", "name": "How do you get an instance of the registry?", "acceptedAnswer": { "@type": "Answer", "text": "Use $registry = Zend_Registry::getInstance(); and then access variables such as $registry->startTime or $registry->requestAction." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/zend-session-usage-in-dotkernel-refactor-of-dot-session-class.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/zend-session-usage-in-dotkernel-refactor-of-dot-session-class.jsonld.twig index 4f134e25..b5f2a001 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/zend-session-usage-in-dotkernel-refactor-of-dot-session-class.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/zend-session-usage-in-dotkernel-refactor-of-dot-session-class.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What session bug was found in Dotkernel 1.5.0?", "acceptedAnswer": { "@type": "Answer", "text": "In unknown circumstances, and only in IE8 and IE9, the session cookie was not saved on the client machine, forcing the user to log in over and over again. The only workaround found was to open a new tab with the same page." } }, { "@type": "Question", "name": "What caused the session cookie to be generated multiple times?", "acceptedAnswer": { "@type": "Answer", "text": "Investigation of the Dot_Session class showed the session cookie was generated 3 times, because the code called both regenerateID() and rememberMe() methods of Zend_Session. According to the Zend Framework documentation, this is unnecessary: if you call rememberMe(), you should not also call regenerateId(), since rememberMe() already calls it internally." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/zend-studio-php-formatter-file-for-dotkernel-coding-standard.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/zend-studio-php-formatter-file-for-dotkernel-coding-standard.jsonld.twig index c4dd056d..a4fbcd0c 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/zend-studio-php-formatter-file-for-dotkernel-coding-standard.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/zend-studio-php-formatter-file-for-dotkernel-coding-standard.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", diff --git a/src/Blog/templates/page/JSON-LD/dotkernel3/development-report-december-11-2017.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel3/development-report-december-11-2017.jsonld.twig index dd0525b0..f27347b8 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel3/development-report-december-11-2017.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel3/development-report-december-11-2017.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What documentation updates does this report cover?", "acceptedAnswer": { "@type": "Answer", "text": "Release notes were added for Dotkernel3 frontend and admin, a Webpack tutorial was added to the Prerequisites section, and the Api Endpoint Documentation Guidelines were updated with a slightly different layout and some corrections." } }, { "@type": "Question", "name": "Who was credited as a contributor in this report?", "acceptedAnswer": { "@type": "Answer", "text": "JapSeyz was thanked as a contributor." } } diff --git a/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel-admin-v4.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel-admin-v4.jsonld.twig index 4b8ef3cd..35a928d6 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel-admin-v4.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel-admin-v4.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "When was Dotkernel Admin V4 released, and what is it built on?", "acceptedAnswer": { "@type": "Answer", "text": "Dotkernel Admin V4 was officially released on 19 July 2022. It's Dotkernel's PSR-7 Admin, an application based on Mezzio, mainly for managing and displaying tabular data from one or more database components." } }, { "@type": "Question", "name": "What are the demo credentials for trying out Dotkernel Admin V4?", "acceptedAnswer": { "@type": "Answer", "text": "Username admin and password dotadmin, on the demo at https://admin4.dotkernel.net/." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel-admin-version-3-launched.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel-admin-version-3-launched.jsonld.twig index 1b0abf11..2e4b39c1 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel-admin-version-3-launched.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel-admin-version-3-launched.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What is Dotkernel Admin?", "acceptedAnswer": { "@type": "Answer", "text": "A basic admin panel based on Bootstrap ^4.5.0, using Doctrine to perform basic CRUD operations over a database, built as a collection of PSR-7 Middleware applications on top of Mezzio and Laminas components." } }, { "@type": "Question", "name": "Where can you see a live demo of version 3?", "acceptedAnswer": { "@type": "Answer", "text": "At admin.dotkernel.net." } }, diff --git a/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel-api-architecture-and-components.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel-api-architecture-and-components.jsonld.twig index e38c13fe..e87ea3ab 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel-api-architecture-and-components.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel-api-architecture-and-components.jsonld.twig @@ -2,8 +2,8 @@ { "@context": "https://schema.org", "@type": "BlogPosting", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "headline": "{{ article.title }}", "description": "{{ article.excerpt }}", "datePublished": "{{ article.postDate|date('c') }}", @@ -12,15 +12,15 @@ "author": { "@type": "Person", "name": "{{ article.author.name }}", - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } } diff --git a/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel-frontend-version-3-launched.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel-frontend-version-3-launched.jsonld.twig index e38c13fe..e87ea3ab 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel-frontend-version-3-launched.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel-frontend-version-3-launched.jsonld.twig @@ -2,8 +2,8 @@ { "@context": "https://schema.org", "@type": "BlogPosting", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "headline": "{{ article.title }}", "description": "{{ article.excerpt }}", "datePublished": "{{ article.postDate|date('c') }}", @@ -12,15 +12,15 @@ "author": { "@type": "Person", "name": "{{ article.author.name }}", - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } } diff --git a/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel3-stable-release-version-1-0.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel3-stable-release-version-1-0.jsonld.twig index e38c13fe..e87ea3ab 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel3-stable-release-version-1-0.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel3-stable-release-version-1-0.jsonld.twig @@ -2,8 +2,8 @@ { "@context": "https://schema.org", "@type": "BlogPosting", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "headline": "{{ article.title }}", "description": "{{ article.excerpt }}", "datePublished": "{{ article.postDate|date('c') }}", @@ -12,15 +12,15 @@ "author": { "@type": "Person", "name": "{{ article.author.name }}", - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } } diff --git a/src/Blog/templates/page/JSON-LD/dotkernel3/php-8-3-support-in-dotkernel-admin.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel3/php-8-3-support-in-dotkernel-admin.jsonld.twig index e38c13fe..e87ea3ab 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel3/php-8-3-support-in-dotkernel-admin.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel3/php-8-3-support-in-dotkernel-admin.jsonld.twig @@ -2,8 +2,8 @@ { "@context": "https://schema.org", "@type": "BlogPosting", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "headline": "{{ article.title }}", "description": "{{ article.excerpt }}", "datePublished": "{{ article.postDate|date('c') }}", @@ -12,15 +12,15 @@ "author": { "@type": "Person", "name": "{{ article.author.name }}", - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } } diff --git a/src/Blog/templates/page/JSON-LD/dotkernel3/php-8-3-support-in-dotkernel-api.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel3/php-8-3-support-in-dotkernel-api.jsonld.twig index 165a7b30..819417bd 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel3/php-8-3-support-in-dotkernel-api.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel3/php-8-3-support-in-dotkernel-api.jsonld.twig @@ -4,45 +4,45 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ { "@type": "ListItem", "position": 1, "name": "Home", - "item": "{{ app.baseUrl }}" + "item": "{{ app.url }}" }, { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, - "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" + "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", @@ -53,7 +53,7 @@ }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", diff --git a/src/Blog/templates/page/JSON-LD/dotkernel3/php-8-3-support-in-dotkernel-frontend.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel3/php-8-3-support-in-dotkernel-frontend.jsonld.twig index 1d3fca45..fe336c59 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel3/php-8-3-support-in-dotkernel-frontend.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel3/php-8-3-support-in-dotkernel-frontend.jsonld.twig @@ -4,45 +4,45 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ { "@type": "ListItem", "position": 1, "name": "Home", - "item": "{{ app.baseUrl }}" + "item": "{{ app.url }}" }, { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, - "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" + "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", @@ -53,7 +53,7 @@ }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", diff --git a/src/Blog/templates/page/JSON-LD/headless-platform/complementary-admin-in-dotkernel-headless-platform.jsonld.twig b/src/Blog/templates/page/JSON-LD/headless-platform/complementary-admin-in-dotkernel-headless-platform.jsonld.twig index 8acf42fc..a0c0b466 100644 --- a/src/Blog/templates/page/JSON-LD/headless-platform/complementary-admin-in-dotkernel-headless-platform.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/headless-platform/complementary-admin-in-dotkernel-headless-platform.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What is Dotkernel Admin?", "acceptedAnswer": { "@type": "Answer", "text": "Dotkernel Admin is a fully-functional, independent application that lives in its own separate repository. It's built on the same Mezzio + Laminas foundation as Dotkernel API and includes a built-in UI with authentication, user management and a web interface." } }, { "@type": "Question", "name": "How do I install Dotkernel Admin?", "acceptedAnswer": { "@type": "Answer", "text": "Install it via Git into the folder of your choice with git clone https://github.com/dotkernel/admin.git ., then follow the step-by-step installation guide to configure the database credentials and run the migrations and fixtures that create and populate the database. Be sure to change the default credentials before going to production." } }, diff --git a/src/Blog/templates/page/JSON-LD/headless-platform/dotkernel-api-v6-the-root-of-dotkernel-headless-platform.jsonld.twig b/src/Blog/templates/page/JSON-LD/headless-platform/dotkernel-api-v6-the-root-of-dotkernel-headless-platform.jsonld.twig index b51798ca..42fc8407 100644 --- a/src/Blog/templates/page/JSON-LD/headless-platform/dotkernel-api-v6-the-root-of-dotkernel-headless-platform.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/headless-platform/dotkernel-api-v6-the-root-of-dotkernel-headless-platform.jsonld.twig @@ -4,45 +4,45 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ { "@type": "ListItem", "position": 1, "name": "Home", - "item": "{{ app.baseUrl }}" + "item": "{{ app.url }}" }, { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, - "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" + "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", @@ -53,7 +53,7 @@ }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", diff --git a/src/Blog/templates/page/JSON-LD/headless-platform/dotkernel-headless-platform-the-whats-hows-and-whys.jsonld.twig b/src/Blog/templates/page/JSON-LD/headless-platform/dotkernel-headless-platform-the-whats-hows-and-whys.jsonld.twig index fb45bc13..daa5f565 100644 --- a/src/Blog/templates/page/JSON-LD/headless-platform/dotkernel-headless-platform-the-whats-hows-and-whys.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/headless-platform/dotkernel-headless-platform-the-whats-hows-and-whys.jsonld.twig @@ -4,45 +4,45 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ { "@type": "ListItem", "position": 1, "name": "Home", - "item": "{{ app.baseUrl }}" + "item": "{{ app.url }}" }, { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, - "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" + "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", @@ -53,7 +53,7 @@ }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", diff --git a/src/Blog/templates/page/JSON-LD/headless-platform/dotkernel-queue-asynchronous-execution-in-dotkernel-headless-platform.jsonld.twig b/src/Blog/templates/page/JSON-LD/headless-platform/dotkernel-queue-asynchronous-execution-in-dotkernel-headless-platform.jsonld.twig index ecb9e7de..b2b1b9d1 100644 --- a/src/Blog/templates/page/JSON-LD/headless-platform/dotkernel-queue-asynchronous-execution-in-dotkernel-headless-platform.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/headless-platform/dotkernel-queue-asynchronous-execution-in-dotkernel-headless-platform.jsonld.twig @@ -4,45 +4,45 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ { "@type": "ListItem", "position": 1, "name": "Home", - "item": "{{ app.baseUrl }}" + "item": "{{ app.url }}" }, { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, - "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" + "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", @@ -53,7 +53,7 @@ }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", diff --git a/src/Blog/templates/page/JSON-LD/headless-platform/dotmaker-generate-common-code-in-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/headless-platform/dotmaker-generate-common-code-in-dotkernel.jsonld.twig index 957fbb82..3f63680c 100644 --- a/src/Blog/templates/page/JSON-LD/headless-platform/dotmaker-generate-common-code-in-dotkernel.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/headless-platform/dotmaker-generate-common-code-in-dotkernel.jsonld.twig @@ -4,45 +4,45 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ { "@type": "ListItem", "position": 1, "name": "Home", - "item": "{{ app.baseUrl }}" + "item": "{{ app.url }}" }, { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, - "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" + "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", @@ -53,7 +53,7 @@ }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", diff --git a/src/Blog/templates/page/JSON-LD/headless-platform/evolution-pattern-versus-api-versioning.jsonld.twig b/src/Blog/templates/page/JSON-LD/headless-platform/evolution-pattern-versus-api-versioning.jsonld.twig index 213e0612..770c7898 100644 --- a/src/Blog/templates/page/JSON-LD/headless-platform/evolution-pattern-versus-api-versioning.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/headless-platform/evolution-pattern-versus-api-versioning.jsonld.twig @@ -4,45 +4,45 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ { "@type": "ListItem", "position": 1, "name": "Home", - "item": "{{ app.baseUrl }}" + "item": "{{ app.url }}" }, { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, - "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" + "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", @@ -53,7 +53,7 @@ }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", diff --git a/src/Blog/templates/page/JSON-LD/headless-platform/implementing-time-based-one-time-password-totp-in-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/headless-platform/implementing-time-based-one-time-password-totp-in-dotkernel.jsonld.twig index 5fae0b7f..2e1cbbe5 100644 --- a/src/Blog/templates/page/JSON-LD/headless-platform/implementing-time-based-one-time-password-totp-in-dotkernel.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/headless-platform/implementing-time-based-one-time-password-totp-in-dotkernel.jsonld.twig @@ -4,45 +4,45 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? (app.baseUrl ~ meta.openGraphImage) : app.meta.image }}", + "image": "{{ meta.openGraphImage ? (app.url ~ meta.openGraphImage) : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ { "@type": "ListItem", "position": 1, "name": "Home", - "item": "{{ app.baseUrl }}" + "item": "{{ app.url }}" }, { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, - "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" + "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", @@ -53,7 +53,7 @@ }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", diff --git a/src/Blog/templates/page/JSON-LD/headless-platform/shared-core-submodule-in-dotkernel-headless-platform.jsonld.twig b/src/Blog/templates/page/JSON-LD/headless-platform/shared-core-submodule-in-dotkernel-headless-platform.jsonld.twig index 773fe9af..3b37e074 100644 --- a/src/Blog/templates/page/JSON-LD/headless-platform/shared-core-submodule-in-dotkernel-headless-platform.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/headless-platform/shared-core-submodule-in-dotkernel-headless-platform.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What is the Core submodule in Dotkernel's Headless Platform?", "acceptedAnswer": { "@type": "Answer", "text": "The Core submodule is a common codebase shared by the applications in your project (for example API, Admin and Queue). It ensures each application uses entities and services in the same way, so rather than updating each application separately, you update the relevant service in Core once and sync it across applications." } }, { "@type": "Question", "name": "What is the golden rule for the Core codebase?", "acceptedAnswer": { "@type": "Answer", "text": "The Core codebase is the only place which manages the database entities. As much as possible, all Doctrine entities must reside in Core, whose current location is src/Core." } }, diff --git a/src/Blog/templates/page/JSON-LD/headless-platform/version-7-adds-postgresql-native-uuid-and-php-8-5.jsonld.twig b/src/Blog/templates/page/JSON-LD/headless-platform/version-7-adds-postgresql-native-uuid-and-php-8-5.jsonld.twig index 2ef262f2..02ede974 100644 --- a/src/Blog/templates/page/JSON-LD/headless-platform/version-7-adds-postgresql-native-uuid-and-php-8-5.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/headless-platform/version-7-adds-postgresql-native-uuid-and-php-8-5.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What new features does Dotkernel API v7 introduce?", "acceptedAnswer": { "@type": "Answer", "text": "v7 adds support for UUID v7 using the native UUID data type, support for PostgreSQL, support for PHP 8.5 (API) and 8.4 (Admin), support for a database table prefix, improved database configuration, and replaces the binary data type for id (index) columns in favor of uuid." } }, { "@type": "Question", "name": "What features were removed in v7?", "acceptedAnswer": { "@type": "Answer", "text": "v7 removes the Evolution pattern's support for Method Deprecation, and drops support for MySQL, primarily because MySQL doesn't support UUID as a data type." } }, diff --git a/src/Blog/templates/page/JSON-LD/how-to/cors-policy-setup-in-dotkernel-using-mezzio-cors.jsonld.twig b/src/Blog/templates/page/JSON-LD/how-to/cors-policy-setup-in-dotkernel-using-mezzio-cors.jsonld.twig index 7f0b3c9c..bfe460af 100644 --- a/src/Blog/templates/page/JSON-LD/how-to/cors-policy-setup-in-dotkernel-using-mezzio-cors.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/how-to/cors-policy-setup-in-dotkernel-using-mezzio-cors.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What causes the CORS error described in this article?", "acceptedAnswer": { "@type": "Answer", "text": "It appears when a browser blocks access to a resource on another origin because the server's response doesn't include an 'Access-Control-Allow-Origin' header, per the browser's CORS (Cross-Origin Resource Sharing) policy." } }, { "@type": "Question", "name": "What package solves CORS handling in Dotkernel?", "acceptedAnswer": { "@type": "Answer", "text": "Mezzio CORS, installed by running 'composer require mezzio/mezzio-cors' in the application's root directory." } }, diff --git a/src/Blog/templates/page/JSON-LD/how-to/creating-admin-accounts-in-dotkernel-api.jsonld.twig b/src/Blog/templates/page/JSON-LD/how-to/creating-admin-accounts-in-dotkernel-api.jsonld.twig index d5d822ad..289eb980 100644 --- a/src/Blog/templates/page/JSON-LD/how-to/creating-admin-accounts-in-dotkernel-api.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/how-to/creating-admin-accounts-in-dotkernel-api.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What are the two ways to create an admin account in Dotkernel API?", "acceptedAnswer": { "@type": "Answer", "text": "You can either call the protected API endpoint POST /admin with a JSON body, or run the terminal command php ./bin/cli.php admin:create." } }, { "@type": "Question", "name": "Who can create an admin account via the API endpoint?", "acceptedAnswer": { "@type": "Answer", "text": "This action can be performed only by an authenticated admin/superuser, calling the protected POST /admin endpoint." } }, diff --git a/src/Blog/templates/page/JSON-LD/how-to/database-migrations-and-how-to-use-them.jsonld.twig b/src/Blog/templates/page/JSON-LD/how-to/database-migrations-and-how-to-use-them.jsonld.twig index 0c7de408..131bdf25 100644 --- a/src/Blog/templates/page/JSON-LD/how-to/database-migrations-and-how-to-use-them.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/how-to/database-migrations-and-how-to-use-them.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What is a database migration?", "acceptedAnswer": { "@type": "Answer", "text": "A database migration is a change to the database schema. The Dotkernel stack integrates migrations (and seeders) via a package that adds the php dot command to the application." } }, { "@type": "Question", "name": "What are the benefits of using migrations and seeders?", "acceptedAnswer": { "@type": "Answer", "text": "They keep the database synchronised across the team with minimal effort, so column types and limits stay consistent instead of drifting (e.g. one dev using VARCHAR(50) and another VARCHAR(150)). Deployment scripts can also run migrations automatically in production, avoiding manual SSH access and risky manual database changes." } }, diff --git a/src/Blog/templates/page/JSON-LD/how-to/doctrine-cache-in-mezzio-and-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/how-to/doctrine-cache-in-mezzio-and-dotkernel.jsonld.twig index aa1832d3..fc149965 100644 --- a/src/Blog/templates/page/JSON-LD/how-to/doctrine-cache-in-mezzio-and-dotkernel.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/how-to/doctrine-cache-in-mezzio-and-dotkernel.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "Is this article still current?", "acceptedAnswer": { "@type": "Answer", "text": "The article notes that following version 2 of doctrine/cache, an updated version of this article covering Doctrine cache using Symfony Cache was published in 2024." } }, { "@type": "Question", "name": "Which three Doctrine cache types does this configure?", "acceptedAnswer": { "@type": "Answer", "text": "query_cache, metadata_cache and result_cache, configured through psr/container using PhpFileCache as the cache type." } }, diff --git a/src/Blog/templates/page/JSON-LD/how-to/generating-a-doctrine-migration-without-dropping-custom-tables.jsonld.twig b/src/Blog/templates/page/JSON-LD/how-to/generating-a-doctrine-migration-without-dropping-custom-tables.jsonld.twig index 28cb3cdd..9d6605ca 100644 --- a/src/Blog/templates/page/JSON-LD/how-to/generating-a-doctrine-migration-without-dropping-custom-tables.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/how-to/generating-a-doctrine-migration-without-dropping-custom-tables.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "Why does doctrine-migrations diff try to drop custom tables?", "acceptedAnswer": { "@type": "Answer", "text": "Because those tables are unmapped (there's no Doctrine entity describing them), so diff generates queries that drop them when it creates a new migration file." } }, { "@type": "Question", "name": "How do I avoid dropping unmapped tables when generating a migration?", "acceptedAnswer": { "@type": "Answer", "text": "Delete the migration containing the DROP queries, then re-run the diff command with a filter-expression parameter, e.g. vendor/bin/doctrine-migrations diff --filter-expression='/^(?!oauth_)/' on Linux/macOS (single quotes) or with double quotes on Windows." } }, diff --git a/src/Blog/templates/page/JSON-LD/how-to/implementation-of-seo-friendly-url-in-an-generic-laminas-mezzio-app.jsonld.twig b/src/Blog/templates/page/JSON-LD/how-to/implementation-of-seo-friendly-url-in-an-generic-laminas-mezzio-app.jsonld.twig index 53a43c5f..9ffbe451 100644 --- a/src/Blog/templates/page/JSON-LD/how-to/implementation-of-seo-friendly-url-in-an-generic-laminas-mezzio-app.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/how-to/implementation-of-seo-friendly-url-in-an-generic-laminas-mezzio-app.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What are the benefits of human-readable slugs cited in this article?", "acceptedAnswer": { "@type": "Answer", "text": "Readability and a more user-friendly experience, better search engine optimization (SEO), and improved shareability and trustworthiness of the URL." } }, { "@type": "Question", "name": "What package is used to generate slugs in this example?", "acceptedAnswer": { "@type": "Answer", "text": "gedmo/doctrine-extensions, installed by running composer require gedmo/doctrine-extensions." } }, diff --git a/src/Blog/templates/page/JSON-LD/how-to/installing-almalinux-10-in-wsl2-php-mariadb-composer-phpmyadmin.jsonld.twig b/src/Blog/templates/page/JSON-LD/how-to/installing-almalinux-10-in-wsl2-php-mariadb-composer-phpmyadmin.jsonld.twig index edf5a479..143a3aa1 100644 --- a/src/Blog/templates/page/JSON-LD/how-to/installing-almalinux-10-in-wsl2-php-mariadb-composer-phpmyadmin.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/how-to/installing-almalinux-10-in-wsl2-php-mariadb-composer-phpmyadmin.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What does this WSL recipe set up?", "acceptedAnswer": { "@type": "Answer", "text": "It sets up a full PHP development environment: WSL2, AlmaLinux 10, PHP, Apache, MariaDB, Git, Composer, Node.js and PhpMyAdmin." } }, { "@type": "Question", "name": "What are the OS and hardware requirements?", "acceptedAnswer": { "@type": "Answer", "text": "Windows 10 (Version 1903+ / Build 18362.1049+ for x64, or Version 2004+ / Build 19041+ for ARM64) or Windows 11, plus hardware virtualization support. If needed, enable the Virtual Machine Platform feature via dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart in an administrator PowerShell." } }, diff --git a/src/Blog/templates/page/JSON-LD/how-to/list-available-endpoints-in-dotkernel-api-using-dot-cli.jsonld.twig b/src/Blog/templates/page/JSON-LD/how-to/list-available-endpoints-in-dotkernel-api-using-dot-cli.jsonld.twig index e38c13fe..e87ea3ab 100644 --- a/src/Blog/templates/page/JSON-LD/how-to/list-available-endpoints-in-dotkernel-api-using-dot-cli.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/how-to/list-available-endpoints-in-dotkernel-api-using-dot-cli.jsonld.twig @@ -2,8 +2,8 @@ { "@context": "https://schema.org", "@type": "BlogPosting", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "headline": "{{ article.title }}", "description": "{{ article.excerpt }}", "datePublished": "{{ article.postDate|date('c') }}", @@ -12,15 +12,15 @@ "author": { "@type": "Person", "name": "{{ article.author.name }}", - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } } diff --git a/src/Blog/templates/page/JSON-LD/how-to/migrating-dotkernel-3-from-zend-expressive-2-to-zend-expressive-3.jsonld.twig b/src/Blog/templates/page/JSON-LD/how-to/migrating-dotkernel-3-from-zend-expressive-2-to-zend-expressive-3.jsonld.twig index e38c13fe..e87ea3ab 100644 --- a/src/Blog/templates/page/JSON-LD/how-to/migrating-dotkernel-3-from-zend-expressive-2-to-zend-expressive-3.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/how-to/migrating-dotkernel-3-from-zend-expressive-2-to-zend-expressive-3.jsonld.twig @@ -2,8 +2,8 @@ { "@context": "https://schema.org", "@type": "BlogPosting", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "headline": "{{ article.title }}", "description": "{{ article.excerpt }}", "datePublished": "{{ article.postDate|date('c') }}", @@ -12,15 +12,15 @@ "author": { "@type": "Person", "name": "{{ article.author.name }}", - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } } diff --git a/src/Blog/templates/page/JSON-LD/how-to/replacing-dot-console-with-dot-cli-based-on-laminas-cli.jsonld.twig b/src/Blog/templates/page/JSON-LD/how-to/replacing-dot-console-with-dot-cli-based-on-laminas-cli.jsonld.twig index e38c13fe..e87ea3ab 100644 --- a/src/Blog/templates/page/JSON-LD/how-to/replacing-dot-console-with-dot-cli-based-on-laminas-cli.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/how-to/replacing-dot-console-with-dot-cli-based-on-laminas-cli.jsonld.twig @@ -2,8 +2,8 @@ { "@context": "https://schema.org", "@type": "BlogPosting", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "headline": "{{ article.title }}", "description": "{{ article.excerpt }}", "datePublished": "{{ article.postDate|date('c') }}", @@ -12,15 +12,15 @@ "author": { "@type": "Person", "name": "{{ article.author.name }}", - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } } diff --git a/src/Blog/templates/page/JSON-LD/how-to/using-postman-for-documentation-in-dotkernel-api-3.jsonld.twig b/src/Blog/templates/page/JSON-LD/how-to/using-postman-for-documentation-in-dotkernel-api-3.jsonld.twig index e38c13fe..e87ea3ab 100644 --- a/src/Blog/templates/page/JSON-LD/how-to/using-postman-for-documentation-in-dotkernel-api-3.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/how-to/using-postman-for-documentation-in-dotkernel-api-3.jsonld.twig @@ -2,8 +2,8 @@ { "@context": "https://schema.org", "@type": "BlogPosting", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "headline": "{{ article.title }}", "description": "{{ article.excerpt }}", "datePublished": "{{ article.postDate|date('c') }}", @@ -12,15 +12,15 @@ "author": { "@type": "Person", "name": "{{ article.author.name }}", - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } } diff --git a/src/Blog/templates/page/JSON-LD/how-to/using-the-urlgenerator-work-in-fastroute.jsonld.twig b/src/Blog/templates/page/JSON-LD/how-to/using-the-urlgenerator-work-in-fastroute.jsonld.twig index e38c13fe..e87ea3ab 100644 --- a/src/Blog/templates/page/JSON-LD/how-to/using-the-urlgenerator-work-in-fastroute.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/how-to/using-the-urlgenerator-work-in-fastroute.jsonld.twig @@ -2,8 +2,8 @@ { "@context": "https://schema.org", "@type": "BlogPosting", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "headline": "{{ article.title }}", "description": "{{ article.excerpt }}", "datePublished": "{{ article.postDate|date('c') }}", @@ -12,15 +12,15 @@ "author": { "@type": "Person", "name": "{{ article.author.name }}", - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } } diff --git a/src/Blog/templates/page/JSON-LD/how-to/what-is-cross-origin-token-redemption.jsonld.twig b/src/Blog/templates/page/JSON-LD/how-to/what-is-cross-origin-token-redemption.jsonld.twig index e38c13fe..e87ea3ab 100644 --- a/src/Blog/templates/page/JSON-LD/how-to/what-is-cross-origin-token-redemption.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/how-to/what-is-cross-origin-token-redemption.jsonld.twig @@ -2,8 +2,8 @@ { "@context": "https://schema.org", "@type": "BlogPosting", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "headline": "{{ article.title }}", "description": "{{ article.excerpt }}", "datePublished": "{{ article.postDate|date('c') }}", @@ -12,15 +12,15 @@ "author": { "@type": "Person", "name": "{{ article.author.name }}", - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } } diff --git a/src/Blog/templates/page/JSON-LD/how-to/what-is-psr-7-and-how-to-use-it.jsonld.twig b/src/Blog/templates/page/JSON-LD/how-to/what-is-psr-7-and-how-to-use-it.jsonld.twig index e38c13fe..e87ea3ab 100644 --- a/src/Blog/templates/page/JSON-LD/how-to/what-is-psr-7-and-how-to-use-it.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/how-to/what-is-psr-7-and-how-to-use-it.jsonld.twig @@ -2,8 +2,8 @@ { "@context": "https://schema.org", "@type": "BlogPosting", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "headline": "{{ article.title }}", "description": "{{ article.excerpt }}", "datePublished": "{{ article.postDate|date('c') }}", @@ -12,15 +12,15 @@ "author": { "@type": "Person", "name": "{{ article.author.name }}", - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } } diff --git a/src/Blog/templates/page/JSON-LD/javascript/codelobster-php-edition-free-php-html-css-javascript-editor-ide.jsonld.twig b/src/Blog/templates/page/JSON-LD/javascript/codelobster-php-edition-free-php-html-css-javascript-editor-ide.jsonld.twig index e38c13fe..e87ea3ab 100644 --- a/src/Blog/templates/page/JSON-LD/javascript/codelobster-php-edition-free-php-html-css-javascript-editor-ide.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/javascript/codelobster-php-edition-free-php-html-css-javascript-editor-ide.jsonld.twig @@ -2,8 +2,8 @@ { "@context": "https://schema.org", "@type": "BlogPosting", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "headline": "{{ article.title }}", "description": "{{ article.excerpt }}", "datePublished": "{{ article.postDate|date('c') }}", @@ -12,15 +12,15 @@ "author": { "@type": "Person", "name": "{{ article.author.name }}", - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } } diff --git a/src/Blog/templates/page/JSON-LD/javascript/intro-to-jquery.jsonld.twig b/src/Blog/templates/page/JSON-LD/javascript/intro-to-jquery.jsonld.twig index e38c13fe..e87ea3ab 100644 --- a/src/Blog/templates/page/JSON-LD/javascript/intro-to-jquery.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/javascript/intro-to-jquery.jsonld.twig @@ -2,8 +2,8 @@ { "@context": "https://schema.org", "@type": "BlogPosting", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "headline": "{{ article.title }}", "description": "{{ article.excerpt }}", "datePublished": "{{ article.postDate|date('c') }}", @@ -12,15 +12,15 @@ "author": { "@type": "Person", "name": "{{ article.author.name }}", - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } } diff --git a/src/Blog/templates/page/JSON-LD/javascript/javascript-email-validator.jsonld.twig b/src/Blog/templates/page/JSON-LD/javascript/javascript-email-validator.jsonld.twig index 8fb3365e..a13fefdb 100644 --- a/src/Blog/templates/page/JSON-LD/javascript/javascript-email-validator.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/javascript/javascript-email-validator.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What problem does this email validator solve?", "acceptedAnswer": { "@type": "Answer", "text": "Common email regex patterns fail to allow the plus (+) character in the username and the dash (-) character anywhere in the address or domain. The plus sign is used by many people, especially on Gmail, for categorization, and dashes commonly appear in domain names, so a validator that rejects them is too strict." } }, { "@type": "Question", "name": "What is the suggested regex solution?", "acceptedAnswer": { "@type": "Answer", "text": "The article proposes the regular expression ^+(\\.+)*@+(\\.+)*\\.({2,})$ (case-insensitive) as a replacement that permits both the plus and dash characters in the appropriate parts of the address." } }, diff --git a/src/Blog/templates/page/JSON-LD/licensing/mit-versus-lgpl-in-practice-dotkernel-case.jsonld.twig b/src/Blog/templates/page/JSON-LD/licensing/mit-versus-lgpl-in-practice-dotkernel-case.jsonld.twig index 66704a16..1625e92d 100644 --- a/src/Blog/templates/page/JSON-LD/licensing/mit-versus-lgpl-in-practice-dotkernel-case.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/licensing/mit-versus-lgpl-in-practice-dotkernel-case.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "Why did Dotkernel need a workaround for this package?", "acceptedAnswer": { "@type": "Answer", "text": "An analysis found that an upstream package, matomo/device-detector, was licensed under LGPL v3, which requires developers to share their derivative code publicly. Since Dotkernel's own projects use the MIT license, and the more restrictive license would have to apply to the whole application, this conflicted with contractual obligations to keep client code business and enterprise friendly." } }, { "@type": "Question", "name": "Where was the LGPL v3 package being used?", "acceptedAnswer": { "@type": "Answer", "text": "The matomo/device-detector package was used in Dotkernel's dot-user-agent-sniffer package to identify the user agent and use the results in internal reports." } }, diff --git a/src/Blog/templates/page/JSON-LD/middleware/dotkernel-light-improvements-psr-15-handlers-vite-phpstan.jsonld.twig b/src/Blog/templates/page/JSON-LD/middleware/dotkernel-light-improvements-psr-15-handlers-vite-phpstan.jsonld.twig index 712a0862..e50cee7c 100644 --- a/src/Blog/templates/page/JSON-LD/middleware/dotkernel-light-improvements-psr-15-handlers-vite-phpstan.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/middleware/dotkernel-light-improvements-psr-15-handlers-vite-phpstan.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "Why did Dotkernel Light switch from controllers to handlers?", "acceptedAnswer": { "@type": "Answer", "text": "Dotkernel Light switched to PSR-15 handlers instead of dotkernel/dot-controller (which isn't going away, but is no longer used here). Since the request can be filtered or augmented by the time it reaches custom code in a middleware architecture, handlers split the code into more manageable chunks and a cleaner file structure, and make an application's functionality easier to figure out." } }, { "@type": "Question", "name": "What naming pattern is used for the new PSR-15 handlers?", "acceptedAnswer": { "@type": "Answer", "text": "A naming pattern was devised that highlights the method, resource, and action for each handler file, so file names reflect their functionality at a glance, making navigation and onboarding easier." } }, diff --git a/src/Blog/templates/page/JSON-LD/middleware/handling-dynamic-routing-using-fastroute-in-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/middleware/handling-dynamic-routing-using-fastroute-in-dotkernel.jsonld.twig index ca46ff1f..2e7b0c87 100644 --- a/src/Blog/templates/page/JSON-LD/middleware/handling-dynamic-routing-using-fastroute-in-dotkernel.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/middleware/handling-dynamic-routing-using-fastroute-in-dotkernel.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What is the goal of this dynamic routing update?", "acceptedAnswer": { "@type": "Answer", "text": "The goal is to replace the static way of creating routes with a more dynamic implementation, resulting in a cleaner approach that is easier to set up and review at a glance." } }, { "@type": "Question", "name": "How were routes configured in the old, static approach?", "acceptedAnswer": { "@type": "Answer", "text": "Each module had its own RoutesDelegator.php file in the src folder, with hard-coded route entries. Each route needed a method, a path used to direct execution to a handler, a handler like GetPageViewHandler, and a unique route name. In a live application the route list can grow to many entries grouped across modules, meaning multiple RoutesDelegators had to be checked when a routing error occurred." } }, diff --git a/src/Blog/templates/page/JSON-LD/middleware/replacing-controllers-with-psr-15-compliant-handlers-in-dotkernel-light.jsonld.twig b/src/Blog/templates/page/JSON-LD/middleware/replacing-controllers-with-psr-15-compliant-handlers-in-dotkernel-light.jsonld.twig index ee060db2..26b5ea9d 100644 --- a/src/Blog/templates/page/JSON-LD/middleware/replacing-controllers-with-psr-15-compliant-handlers-in-dotkernel-light.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/middleware/replacing-controllers-with-psr-15-compliant-handlers-in-dotkernel-light.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What is the goal of replacing controllers with PSR-15 handlers?", "acceptedAnswer": { "@type": "Answer", "text": "The goal is to implement PSR-15 handlers into Dotkernel Light, keeping the application up-to-date with recommended design guidelines, ensuring it stays secure while implementing standards widely adopted by the PHP community." } }, { "@type": "Question", "name": "What makes handlers better than controllers?", "acceptedAnswer": { "@type": "Answer", "text": "Handlers split code into manageable chunks instead of one large controller file with several actions, following the Single-responsibility Principle (the S in SOLID). Each action becomes its own class, making handlers easier to maintain, refactor and test, easier to expand with new functionality, and simpler to write focused tests for." } }, diff --git a/src/Blog/templates/page/JSON-LD/php-development/almalinux-9-in-wsl2-install-php-apache-mariadb-composer-phpmyadmin.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/almalinux-9-in-wsl2-install-php-apache-mariadb-composer-phpmyadmin.jsonld.twig index 1151ad60..e8cf1a4f 100644 --- a/src/Blog/templates/page/JSON-LD/php-development/almalinux-9-in-wsl2-install-php-apache-mariadb-composer-phpmyadmin.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/php-development/almalinux-9-in-wsl2-install-php-apache-mariadb-composer-phpmyadmin.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "How do I check if my machine is ready for WSL2?", "acceptedAnswer": { "@type": "Answer", "text": "Open Windows Terminal and run wsl -v. If it returns version information, WSL2 is installed. If you get an error instead, WSL is not completely installed and you need to follow the linked setup guide first." } }, { "@type": "Question", "name": "How do I download and install AlmaLinux 9?", "acceptedAnswer": { "@type": "Answer", "text": "Open Microsoft Store, search for AlmaLinux, select AlmaLinux 9, and install it. Clicking Open launches it in Windows Terminal, where the installer prompts for your username, password, and password confirmation." } }, diff --git a/src/Blog/templates/page/JSON-LD/php-development/aptana-php-installation-in-aptana-2-x.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/aptana-php-installation-in-aptana-2-x.jsonld.twig index e38c13fe..e87ea3ab 100644 --- a/src/Blog/templates/page/JSON-LD/php-development/aptana-php-installation-in-aptana-2-x.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/php-development/aptana-php-installation-in-aptana-2-x.jsonld.twig @@ -2,8 +2,8 @@ { "@context": "https://schema.org", "@type": "BlogPosting", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "headline": "{{ article.title }}", "description": "{{ article.excerpt }}", "datePublished": "{{ article.postDate|date('c') }}", @@ -12,15 +12,15 @@ "author": { "@type": "Person", "name": "{{ article.author.name }}", - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } } diff --git a/src/Blog/templates/page/JSON-LD/php-development/better-unicode-support-in-mysql-5-5-utf8mb4.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/better-unicode-support-in-mysql-5-5-utf8mb4.jsonld.twig index e38c13fe..e87ea3ab 100644 --- a/src/Blog/templates/page/JSON-LD/php-development/better-unicode-support-in-mysql-5-5-utf8mb4.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/php-development/better-unicode-support-in-mysql-5-5-utf8mb4.jsonld.twig @@ -2,8 +2,8 @@ { "@context": "https://schema.org", "@type": "BlogPosting", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "headline": "{{ article.title }}", "description": "{{ article.excerpt }}", "datePublished": "{{ article.postDate|date('c') }}", @@ -12,15 +12,15 @@ "author": { "@type": "Person", "name": "{{ article.author.name }}", - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } } diff --git a/src/Blog/templates/page/JSON-LD/php-development/database-seeding-doctrine-data-fixtures-vs-phinx.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/database-seeding-doctrine-data-fixtures-vs-phinx.jsonld.twig index e38c13fe..e87ea3ab 100644 --- a/src/Blog/templates/page/JSON-LD/php-development/database-seeding-doctrine-data-fixtures-vs-phinx.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/php-development/database-seeding-doctrine-data-fixtures-vs-phinx.jsonld.twig @@ -2,8 +2,8 @@ { "@context": "https://schema.org", "@type": "BlogPosting", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "headline": "{{ article.title }}", "description": "{{ article.excerpt }}", "datePublished": "{{ article.postDate|date('c') }}", @@ -12,15 +12,15 @@ "author": { "@type": "Person", "name": "{{ article.author.name }}", - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } } diff --git a/src/Blog/templates/page/JSON-LD/php-development/end-of-support-for-php-5-2-x-branch.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/end-of-support-for-php-5-2-x-branch.jsonld.twig index 8a6b46fa..ea129cb2 100644 --- a/src/Blog/templates/page/JSON-LD/php-development/end-of-support-for-php-5-2-x-branch.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/php-development/end-of-support-for-php-5-2-x-branch.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What does the release of PHP 5.2.14 mean for the 5.2.x branch?", "acceptedAnswer": { "@type": "Answer", "text": "PHP 5.2.14 marks the end of active support for the PHP 5.2 branch. After this release, PHP 5.2 will receive no further active bug maintenance, though security fixes might still be published on a case-by-case basis." } }, { "@type": "Question", "name": "What should users of PHP 5.2 do?", "acceptedAnswer": { "@type": "Answer", "text": "All users of PHP 5.2 are encouraged to upgrade to PHP 5.3." } }, diff --git a/src/Blog/templates/page/JSON-LD/php-development/floating-point-arithmetic-why-is-int-0-7-0-1-10-7.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/floating-point-arithmetic-why-is-int-0-7-0-1-10-7.jsonld.twig index 7dcce1fa..c5629a2d 100644 --- a/src/Blog/templates/page/JSON-LD/php-development/floating-point-arithmetic-why-is-int-0-7-0-1-10-7.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/php-development/floating-point-arithmetic-why-is-int-0-7-0-1-10-7.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "Why does (int)((0.7+0.1)*10) return 7 instead of 8?", "acceptedAnswer": { "@type": "Answer", "text": "Because 0.7 is actually represented internally as 0.699999988079071 and 0.1 as 0.10000000149011612. Adding them gives 0.7999999895691871, and multiplying by 10 gives 7.999999895691871, which truncates to 7 when cast to int." } }, { "@type": "Question", "name": "Does the same rounding issue happen with 0.6 instead of 0.7?", "acceptedAnswer": { "@type": "Answer", "text": "Yes. 0.6 is represented as 0.6000000238418579, and (0.6000000238418579 + 0.10000000149011612) * 10 equals about 7.00000025331974, which also truncates to int(7)." } }, diff --git a/src/Blog/templates/page/JSON-LD/php-development/how-to-upgrade-wamp-to-php-5-3-4.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/how-to-upgrade-wamp-to-php-5-3-4.jsonld.twig index a476ee4c..b198c433 100644 --- a/src/Blog/templates/page/JSON-LD/php-development/how-to-upgrade-wamp-to-php-5-3-4.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/php-development/how-to-upgrade-wamp-to-php-5-3-4.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "Where do I download the PHP 5.3.4 package for WAMP?", "acceptedAnswer": { "@type": "Answer", "text": "Download the latest ZIPPED package for PHP 5.3.4 from windows.php.net. Make sure it is the VC6 Thread Safe build, and do not download the installer." } }, { "@type": "Question", "name": "Where should the downloaded PHP 5.3.4 files be extracted?", "acceptedAnswer": { "@type": "Answer", "text": "Create a folder named php5.3.4 inside wamp/bin/php, then extract the downloaded zip into that newly created folder." } }, diff --git a/src/Blog/templates/page/JSON-LD/php-development/mezzio-app-development-in-wsl2.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/mezzio-app-development-in-wsl2.jsonld.twig index 7a946c4a..eefe2fa9 100644 --- a/src/Blog/templates/page/JSON-LD/php-development/mezzio-app-development-in-wsl2.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/php-development/mezzio-app-development-in-wsl2.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What does this guide help you accomplish?", "acceptedAnswer": { "@type": "Answer", "text": "It walks you through installing a Mezzio application (Dotkernel API) using WSL2 and running it on Ubuntu 20.04 LTS." } }, { "@type": "Question", "name": "What do you need before installing Ubuntu inside WSL2?", "acceptedAnswer": { "@type": "Answer", "text": "You first need WSL2 installed on your machine, which you can set up by following the linked WSL2 installation guide, before installing Ubuntu inside it." } }, diff --git a/src/Blog/templates/page/JSON-LD/php-development/php-5-3-6-released-no-upgrade-possible-for-wampserver.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/php-5-3-6-released-no-upgrade-possible-for-wampserver.jsonld.twig index c35c7389..85c80d94 100644 --- a/src/Blog/templates/page/JSON-LD/php-development/php-5-3-6-released-no-upgrade-possible-for-wampserver.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/php-development/php-5-3-6-released-no-upgrade-possible-for-wampserver.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "Why can't WampServer users upgrade to PHP 5.3.6?", "acceptedAnswer": { "@type": "Answer", "text": "PHP 5.3.6 no longer provides builds created with Visual Studio C++ 6 for Windows users. Since WampServer is built using VC++ 6, upgrading isn't possible without rewriting the entire WampServer package from scratch." } }, { "@type": "Question", "name": "Where can WampServer users find more details about this issue?", "acceptedAnswer": { "@type": "Answer", "text": "More details are available on the WampServer forum, linked in this article." } } diff --git a/src/Blog/templates/page/JSON-LD/php-development/php-environment-development-staging-production.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/php-environment-development-staging-production.jsonld.twig index cf61c06a..d8d1d11b 100644 --- a/src/Blog/templates/page/JSON-LD/php-development/php-environment-development-staging-production.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/php-development/php-environment-development-staging-production.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What is a \"server environment\" in hosted software development?", "acceptedAnswer": { "@type": "Answer", "text": "It refers to a server tier designated to a specific stage in a release process. The purpose of using different environments is to improve the development, testing and release processes in client-server applications." } }, { "@type": "Question", "name": "What is the development environment used for?", "acceptedAnswer": { "@type": "Answer", "text": "It's the working environment for individual developers or small teams, where the developer works on local host, separate from the rest of the team, allowing changes to be made without worrying about altering the work of other team members." } }, diff --git a/src/Blog/templates/page/JSON-LD/php-development/php-support-back-in-aptana-3-0.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/php-support-back-in-aptana-3-0.jsonld.twig index 9512d97e..673c750c 100644 --- a/src/Blog/templates/page/JSON-LD/php-development/php-support-back-in-aptana-3-0.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/php-development/php-support-back-in-aptana-3-0.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "Is Aptana 3.0 available yet, and where can it be downloaded?", "acceptedAnswer": { "@type": "Answer", "text": "Aptana 3.0 is in beta stage and can be downloaded from the official Aptana site." } }, { "@type": "Question", "name": "Will PHP support in Aptana 3.0 be a separate plugin?", "acceptedAnswer": { "@type": "Answer", "text": "No. According to an announcement on Aptana's forum, PHP support is coming back and is being developed and integrated directly into the Studio 3 core, rather than delivered as a separate plugin." } }, diff --git a/src/Blog/templates/page/JSON-LD/php-development/protection-against-sql-injection-using-pdo-and-zend-framework-part-2.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/protection-against-sql-injection-using-pdo-and-zend-framework-part-2.jsonld.twig index c6ba83ea..615e84d2 100644 --- a/src/Blog/templates/page/JSON-LD/php-development/protection-against-sql-injection-using-pdo-and-zend-framework-part-2.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/php-development/protection-against-sql-injection-using-pdo-and-zend-framework-part-2.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What is the focus of this article?", "acceptedAnswer": { "@type": "Answer", "text": "Following the earlier article about SQL Injection, this article makes a stronger argument for using Zend Framework to handle database access. Zend_Db is the primary class used for accessing the database, but there is more to it: Zend_Db_Statement, Zend_Db_Select, and Zend_Db_Tables." } }, { "@type": "Question", "name": "Does Zend_Db's query() method protect against SQL injection?", "acceptedAnswer": { "@type": "Answer", "text": "query() uses prepared statements internally, but SQL Injection is still possible if the $sql parameter passed to it is dynamically created." } }, diff --git a/src/Blog/templates/page/JSON-LD/php-development/protection-against-sql-injection-using-pdo-and-zend-framework.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/protection-against-sql-injection-using-pdo-and-zend-framework.jsonld.twig index 4f1239d1..d04ef55f 100644 --- a/src/Blog/templates/page/JSON-LD/php-development/protection-against-sql-injection-using-pdo-and-zend-framework.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/php-development/protection-against-sql-injection-using-pdo-and-zend-framework.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What is SQL injection?", "acceptedAnswer": { "@type": "Answer", "text": "SQL injection is a technique that exploits a security vulnerability occurring in the database layer of an application. Usually, user input is not filtered by the script and is passed directly into a SQL statement." } }, { "@type": "Question", "name": "What is PDO and what kind of abstraction does it provide?", "acceptedAnswer": { "@type": "Answer", "text": "PDO (PHP Data Objects) is a database access layer providing a standardized method of access to multiple databases. It provides a data-access abstraction layer, meaning you apply the same functions to issue queries and fetch data regardless of which database you're using. However, PDO does not provide a database abstraction - it doesn't rewrite SQL or emulate missing database features." } }, diff --git a/src/Blog/templates/page/JSON-LD/php-development/remote-connections-to-mysql-server-on-plesk-based-servers.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/remote-connections-to-mysql-server-on-plesk-based-servers.jsonld.twig index acd6c64b..8e31b413 100644 --- a/src/Blog/templates/page/JSON-LD/php-development/remote-connections-to-mysql-server-on-plesk-based-servers.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/php-development/remote-connections-to-mysql-server-on-plesk-based-servers.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "Why can't I connect remotely to MySQL on a Plesk-based server?", "acceptedAnswer": { "@type": "Answer", "text": "By default, MySQL servers on Linux machines with Plesk installed have the old_passwords flag set to 1 (ON). Even on MySQL 5.5+, this means the old, pre-4.1 password storage style is still used, which causes a PDOException error when connecting remotely." } }, { "@type": "Question", "name": "What error message indicates this old-passwords problem?", "acceptedAnswer": { "@type": "Answer", "text": "You get a fatal error mentioning \"cannot connect to MySQL 4.1+ using the old insecure authentication,\" suggesting you reset the password using SET PASSWORD = PASSWORD('your_existing_password') to store a newer, more secure hash." } }, diff --git a/src/Blog/templates/page/JSON-LD/php-development/static-analysis-replacing-psalm-with-phpstan.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/static-analysis-replacing-psalm-with-phpstan.jsonld.twig index 40c522bb..eb8cb359 100644 --- a/src/Blog/templates/page/JSON-LD/php-development/static-analysis-replacing-psalm-with-phpstan.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/php-development/static-analysis-replacing-psalm-with-phpstan.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What is static analysis and why is it used?", "acceptedAnswer": { "@type": "Answer", "text": "Static analysis (or source code analysis) applies coding rules to debug source code before a program is run. It's used to catch and fix type-related errors, confirm coding standards, identify code that needs refactoring, and enhance security by detecting issues like code injection, XSS, and open redirect vulnerabilities." } }, { "@type": "Question", "name": "Why did Dotkernel switch from Psalm to PHPStan?", "acceptedAnswer": { "@type": "Answer", "text": "Dotkernel had used Psalm with positive results, but much of the PHP community, including projects like Doctrine and Composer, moved to PHPStan instead. Since the two tools have largely identical findings, using both wasn't justified, and PHPStan has a faster growing ecosystem, better social media presence, a fulltime contributor (@ondrejmirtes), PHP stubs from PHPStorm, and better quality and depth of detection." } }, diff --git a/src/Blog/templates/page/JSON-LD/php-development/using-aptana-to-connect-to-dotkernel-tracker-mantis.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/using-aptana-to-connect-to-dotkernel-tracker-mantis.jsonld.twig index 9f261ee9..b3af0290 100644 --- a/src/Blog/templates/page/JSON-LD/php-development/using-aptana-to-connect-to-dotkernel-tracker-mantis.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/php-development/using-aptana-to-connect-to-dotkernel-tracker-mantis.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What is Dotkernel Tracker?", "acceptedAnswer": { "@type": "Answer", "text": "Dotkernel Tracker is a Mantis-based, web-based bugtracking system used by the Dotkernel application framework, where bugs are reported, new features are announced, and other general tickets are added." } }, { "@type": "Question", "name": "How is Aptana integrated with Dotkernel Tracker?", "acceptedAnswer": { "@type": "Answer", "text": "Aptana, the web development IDE used for Dotkernel development, is integrated with Dotkernel Tracker via the Mylyn plugin's Mantis connector, so tickets can be managed directly from within the IDE." } }, diff --git a/src/Blog/templates/page/JSON-LD/php-development/using-php-7-express-in-zend-studio-13.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/using-php-7-express-in-zend-studio-13.jsonld.twig index cdc55b59..144197be 100644 --- a/src/Blog/templates/page/JSON-LD/php-development/using-php-7-express-in-zend-studio-13.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/php-development/using-php-7-express-in-zend-studio-13.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What is PHP 7 Express in Zend Studio?", "acceptedAnswer": { "@type": "Answer", "text": "PHP 7 Express is a feature added in Zend Studio starting with version 13. It's useful for checking whether pre-PHP7 code will work correctly on a PHP7 server." } }, { "@type": "Question", "name": "What PHP version should you select when creating a test project?", "acceptedAnswer": { "@type": "Answer", "text": "Use the exact PHP version your project actually uses (e.g. PHP 5.6), not PHP 7. Choosing PHP 7 at project creation hides the PHP 7 Express feature, because Zend Studio assumes a PHP 7 project doesn't need compatibility checks." } }, diff --git a/src/Blog/templates/page/JSON-LD/php-development/version-control-ignore-patterns-in-zend-studio.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/version-control-ignore-patterns-in-zend-studio.jsonld.twig index 3219fb78..db8bca15 100644 --- a/src/Blog/templates/page/JSON-LD/php-development/version-control-ignore-patterns-in-zend-studio.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/php-development/version-control-ignore-patterns-in-zend-studio.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "How do you set global ignored resource patterns in Zend Studio?", "acceptedAnswer": { "@type": "Answer", "text": "Go to Window -> Preferences, then Team -> Ignored Resources. From there, use Add Pattern to add ignored patterns one by one, applying them globally to all projects instead of adding them manually to each project, or remove existing patterns." } }, { "@type": "Question", "name": "When is this global ignore configuration especially useful?", "acceptedAnswer": { "@type": "Answer", "text": "It's especially useful when you have both Git and SVN projects in the same workspace, and also when you tend to be less careful about your code and workspace. On each project you can still choose to use either the global ignored patterns or specific ones." } } diff --git a/src/Blog/templates/page/JSON-LD/php-development/welcome-to-the-10th-zend-certified-engineer-in-dotboost-team.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/welcome-to-the-10th-zend-certified-engineer-in-dotboost-team.jsonld.twig index 5a8f9797..cb38e280 100644 --- a/src/Blog/templates/page/JSON-LD/php-development/welcome-to-the-10th-zend-certified-engineer-in-dotboost-team.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/php-development/welcome-to-the-10th-zend-certified-engineer-in-dotboost-team.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What milestone is this article announcing?", "acceptedAnswer": { "@type": "Answer", "text": "Dotboost Technologies Inc. is announcing that the 10th member of its team has passed the Zend Certified Engineer (ZCE) exam, a milestone the company highlights as part of its commitment to top-level PHP development for clients." } }, { "@type": "Question", "name": "What are the team's next certification-related plans?", "acceptedAnswer": { "@type": "Answer", "text": "The next step is to implement Zend Framework 2 best practices and to pursue the Zend Framework 2 Certified Architect exam. Additionally, starting in 2014, Zend Certification will become mandatory for every developer on the team." } } diff --git a/src/Blog/templates/page/JSON-LD/php-development/zend-server-5-5-quick-setup-on-windows.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/zend-server-5-5-quick-setup-on-windows.jsonld.twig index c782c7f3..73842d4d 100644 --- a/src/Blog/templates/page/JSON-LD/php-development/zend-server-5-5-quick-setup-on-windows.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/php-development/zend-server-5-5-quick-setup-on-windows.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "How do you enable mod_rewrite after a fresh Zend Server 5.5 install on Windows?", "acceptedAnswer": { "@type": "Answer", "text": "Open ZEND_INSTALATION_PATH/Apache2/conf/httpd.conf, change the AllowOverride directive from None to All, then restart the Apache service." } }, { "@type": "Question", "name": "What PHP directive changes are recommended in the Zend Server admin interface?", "acceptedAnswer": { "@type": "Answer", "text": "In the Zend Server administration interface (http://localhost:10081/ZendServer/), under Server Setup -> Directives, set the date timezone to America/New_York, set display_errors to on, and set error_reporting to -1." } }, diff --git a/src/Blog/templates/page/JSON-LD/php-troubleshooting/fix-installing-pear-packages-with-php-7-2.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-troubleshooting/fix-installing-pear-packages-with-php-7-2.jsonld.twig index fd521268..8c681244 100644 --- a/src/Blog/templates/page/JSON-LD/php-troubleshooting/fix-installing-pear-packages-with-php-7-2.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/php-troubleshooting/fix-installing-pear-packages-with-php-7-2.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What error occurs when installing PEAR packages with PHP 7.2?", "acceptedAnswer": { "@type": "Answer", "text": "Running a command like pear install PHP_CodeSniffer produces the fatal error \"Cannot use result of built-in function in write context\" in Archive/Tar.php on line 639, because the function is called by reference." } }, { "@type": "Question", "name": "How do you fix the \"Cannot use result of built-in function in write context\" error?", "acceptedAnswer": { "@type": "Answer", "text": "Go to the line indicated in the error (line 639) and replace \"$v_att_list = &func_get_args();\" with \"$v_att_list = func_get_args();\", so func_get_args() is no longer called by reference." } }, diff --git a/src/Blog/templates/page/JSON-LD/php-troubleshooting/where-is-the-intl-php-extension-problem-solved.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-troubleshooting/where-is-the-intl-php-extension-problem-solved.jsonld.twig index d37b59e5..6cbee1f9 100644 --- a/src/Blog/templates/page/JSON-LD/php-troubleshooting/where-is-the-intl-php-extension-problem-solved.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/php-troubleshooting/where-is-the-intl-php-extension-problem-solved.jsonld.twig @@ -4,42 +4,42 @@ "@graph": [ { "@type": "TechArticle", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", "headline": {{ article.title|json_encode|raw }}, "description": {{ article.excerpt|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "datePublished": "{{ article.postDate|date('c') }}", "dateModified": "{{ article.postDate|date('c') }}", - "image": "{{ meta.openGraphImage ? app.baseUrl ~ meta.openGraphImage : app.meta.image }}", + "image": "{{ meta.openGraphImage ? app.url ~ meta.openGraphImage : app.meta.image }}", "articleSection": {{ article.category.name|json_encode|raw }}, "author": { "@type": "Person", "name": {{ article.author.name|json_encode|raw }}, - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } }, { "@type": "BreadcrumbList", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#breadcrumb", "itemListElement": [ - { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.baseUrl }}" }, - { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.baseUrl ~ path('page::category-resource', {slug: article.category.slug}) }}" }, + { "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ app.url }}" }, + { "@type": "ListItem", "position": 2, "name": {{ article.category.name|json_encode|raw }}, "item": "{{ app.url ~ path('page::category-resource', {slug: article.category.slug}) }}" }, { "@type": "ListItem", "position": 3, "name": {{ article.title|json_encode|raw }} } ] }, { "@type": "FAQPage", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ { "@type": "Question", "name": "What errors indicate the PHP Intl extension is missing?", "acceptedAnswer": { "@type": "Answer", "text": "Typical errors include \"Zend InputFilter requires intl PHP extension\" and \"The requested PHP extension intl is missing from your system.\" These happen because the PHP Intl extension isn't installed or enabled." } }, { "@type": "Question", "name": "What is the PHP Intl extension used for?", "acceptedAnswer": { "@type": "Answer", "text": "Intl (Internationalization extension) is a wrapper for the ICU library that lets PHP programmers perform locale-aware operations, including formatting, transliteration, encoding conversion, and calendar operations. It can be installed bundled since PHP 5.3.0, or as a separate PECL extension since PHP 5.2.0." } }, diff --git a/src/Blog/templates/page/JSON-LD/phpstorm/code-quality-how-to-setup-phpcs-in-phpstorm.jsonld.twig b/src/Blog/templates/page/JSON-LD/phpstorm/code-quality-how-to-setup-phpcs-in-phpstorm.jsonld.twig index e38c13fe..e87ea3ab 100644 --- a/src/Blog/templates/page/JSON-LD/phpstorm/code-quality-how-to-setup-phpcs-in-phpstorm.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/phpstorm/code-quality-how-to-setup-phpcs-in-phpstorm.jsonld.twig @@ -2,8 +2,8 @@ { "@context": "https://schema.org", "@type": "BlogPosting", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "headline": "{{ article.title }}", "description": "{{ article.excerpt }}", "datePublished": "{{ article.postDate|date('c') }}", @@ -12,15 +12,15 @@ "author": { "@type": "Person", "name": "{{ article.author.name }}", - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } } diff --git a/src/Blog/templates/page/JSON-LD/tag-resource.jsonld.twig b/src/Blog/templates/page/JSON-LD/tag-resource.jsonld.twig index fa60d471..46979b1e 100644 --- a/src/Blog/templates/page/JSON-LD/tag-resource.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/tag-resource.jsonld.twig @@ -2,10 +2,10 @@ { "@context": "https://schema.org", "@type": "CollectionPage", - "@id": "{{ app.baseUrl ~ path('page::tag-resource', {slug: tag.slug}) }}#webpage", - "url": "{{ app.baseUrl ~ path('page::tag-resource', {slug: tag.slug}) }}", + "@id": "{{ app.url ~ path('page::tag-resource', {slug: tag.slug}) }}#webpage", + "url": "{{ app.url ~ path('page::tag-resource', {slug: tag.slug}) }}", "name": "{{ tag.name }}", - "isPartOf": { "@id": "{{ app.baseUrl }}#website" }, + "isPartOf": { "@id": "{{ app.url }}#website" }, "mainEntity": { "@type": "ItemList", "itemListElement": [ @@ -13,7 +13,7 @@ { "@type": "ListItem", "position": {{ loop.index }}, - "url": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "url": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "name": "{{ article.title }}" } {% endfor %} diff --git a/src/Blog/templates/page/JSON-LD/zend-framework/scienta-zf-debug-bar-a-very-helpfull-zf-debug-tool.jsonld.twig b/src/Blog/templates/page/JSON-LD/zend-framework/scienta-zf-debug-bar-a-very-helpfull-zf-debug-tool.jsonld.twig index e38c13fe..e87ea3ab 100644 --- a/src/Blog/templates/page/JSON-LD/zend-framework/scienta-zf-debug-bar-a-very-helpfull-zf-debug-tool.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/zend-framework/scienta-zf-debug-bar-a-very-helpfull-zf-debug-tool.jsonld.twig @@ -2,8 +2,8 @@ { "@context": "https://schema.org", "@type": "BlogPosting", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "headline": "{{ article.title }}", "description": "{{ article.excerpt }}", "datePublished": "{{ article.postDate|date('c') }}", @@ -12,15 +12,15 @@ "author": { "@type": "Person", "name": "{{ article.author.name }}", - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } } diff --git a/src/Blog/templates/page/JSON-LD/zend-framework/sunsetting-pear-channel-for-zend-framework-1.jsonld.twig b/src/Blog/templates/page/JSON-LD/zend-framework/sunsetting-pear-channel-for-zend-framework-1.jsonld.twig index e38c13fe..e87ea3ab 100644 --- a/src/Blog/templates/page/JSON-LD/zend-framework/sunsetting-pear-channel-for-zend-framework-1.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/zend-framework/sunsetting-pear-channel-for-zend-framework-1.jsonld.twig @@ -2,8 +2,8 @@ { "@context": "https://schema.org", "@type": "BlogPosting", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "headline": "{{ article.title }}", "description": "{{ article.excerpt }}", "datePublished": "{{ article.postDate|date('c') }}", @@ -12,15 +12,15 @@ "author": { "@type": "Person", "name": "{{ article.author.name }}", - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } } diff --git a/src/Blog/templates/page/JSON-LD/zend-framework/wurfl-php-api-libraries-gpl-versions.jsonld.twig b/src/Blog/templates/page/JSON-LD/zend-framework/wurfl-php-api-libraries-gpl-versions.jsonld.twig index e38c13fe..e87ea3ab 100644 --- a/src/Blog/templates/page/JSON-LD/zend-framework/wurfl-php-api-libraries-gpl-versions.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/zend-framework/wurfl-php-api-libraries-gpl-versions.jsonld.twig @@ -2,8 +2,8 @@ { "@context": "https://schema.org", "@type": "BlogPosting", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "headline": "{{ article.title }}", "description": "{{ article.excerpt }}", "datePublished": "{{ article.postDate|date('c') }}", @@ -12,15 +12,15 @@ "author": { "@type": "Person", "name": "{{ article.author.name }}", - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } } diff --git a/src/Blog/templates/page/JSON-LD/zend-framework/zend-framework-1-12-4-released-with-security-fixes.jsonld.twig b/src/Blog/templates/page/JSON-LD/zend-framework/zend-framework-1-12-4-released-with-security-fixes.jsonld.twig index e38c13fe..e87ea3ab 100644 --- a/src/Blog/templates/page/JSON-LD/zend-framework/zend-framework-1-12-4-released-with-security-fixes.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/zend-framework/zend-framework-1-12-4-released-with-security-fixes.jsonld.twig @@ -2,8 +2,8 @@ { "@context": "https://schema.org", "@type": "BlogPosting", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "headline": "{{ article.title }}", "description": "{{ article.excerpt }}", "datePublished": "{{ article.postDate|date('c') }}", @@ -12,15 +12,15 @@ "author": { "@type": "Person", "name": "{{ article.author.name }}", - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } } diff --git a/src/Blog/templates/page/JSON-LD/zend-framework/zend-framework-1-7-0-released.jsonld.twig b/src/Blog/templates/page/JSON-LD/zend-framework/zend-framework-1-7-0-released.jsonld.twig index e38c13fe..e87ea3ab 100644 --- a/src/Blog/templates/page/JSON-LD/zend-framework/zend-framework-1-7-0-released.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/zend-framework/zend-framework-1-7-0-released.jsonld.twig @@ -2,8 +2,8 @@ { "@context": "https://schema.org", "@type": "BlogPosting", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "headline": "{{ article.title }}", "description": "{{ article.excerpt }}", "datePublished": "{{ article.postDate|date('c') }}", @@ -12,15 +12,15 @@ "author": { "@type": "Person", "name": "{{ article.author.name }}", - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } } diff --git a/src/Blog/templates/page/JSON-LD/zend-framework/zend-framework-1-end-of-life.jsonld.twig b/src/Blog/templates/page/JSON-LD/zend-framework/zend-framework-1-end-of-life.jsonld.twig index e38c13fe..e87ea3ab 100644 --- a/src/Blog/templates/page/JSON-LD/zend-framework/zend-framework-1-end-of-life.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/zend-framework/zend-framework-1-end-of-life.jsonld.twig @@ -2,8 +2,8 @@ { "@context": "https://schema.org", "@type": "BlogPosting", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "headline": "{{ article.title }}", "description": "{{ article.excerpt }}", "datePublished": "{{ article.postDate|date('c') }}", @@ -12,15 +12,15 @@ "author": { "@type": "Person", "name": "{{ article.author.name }}", - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } } diff --git a/src/Blog/templates/page/JSON-LD/zend-framework/zend-framework-as-pear-accessible-repository-on-plesk-server.jsonld.twig b/src/Blog/templates/page/JSON-LD/zend-framework/zend-framework-as-pear-accessible-repository-on-plesk-server.jsonld.twig index e38c13fe..e87ea3ab 100644 --- a/src/Blog/templates/page/JSON-LD/zend-framework/zend-framework-as-pear-accessible-repository-on-plesk-server.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/zend-framework/zend-framework-as-pear-accessible-repository-on-plesk-server.jsonld.twig @@ -2,8 +2,8 @@ { "@context": "https://schema.org", "@type": "BlogPosting", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "headline": "{{ article.title }}", "description": "{{ article.excerpt }}", "datePublished": "{{ article.postDate|date('c') }}", @@ -12,15 +12,15 @@ "author": { "@type": "Person", "name": "{{ article.author.name }}", - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } } diff --git a/src/Blog/templates/page/JSON-LD/zend-framework/zend-mail-and-zend-http-security-fixes-in-zend-framework-1-12-12.jsonld.twig b/src/Blog/templates/page/JSON-LD/zend-framework/zend-mail-and-zend-http-security-fixes-in-zend-framework-1-12-12.jsonld.twig index e38c13fe..e87ea3ab 100644 --- a/src/Blog/templates/page/JSON-LD/zend-framework/zend-mail-and-zend-http-security-fixes-in-zend-framework-1-12-12.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/zend-framework/zend-mail-and-zend-http-security-fixes-in-zend-framework-1-12-12.jsonld.twig @@ -2,8 +2,8 @@ { "@context": "https://schema.org", "@type": "BlogPosting", - "@id": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", - "mainEntityOfPage": "{{ app.baseUrl ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", + "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#article", + "mainEntityOfPage": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}", "headline": "{{ article.title }}", "description": "{{ article.excerpt }}", "datePublished": "{{ article.postDate|date('c') }}", @@ -12,15 +12,15 @@ "author": { "@type": "Person", "name": "{{ article.author.name }}", - "url": "{{ app.baseUrl ~ path('page::author-resource', {slug: article.author.slug}) }}" + "url": "{{ app.url ~ path('page::author-resource', {slug: article.author.slug}) }}" }, "publisher": { "@type": "Organization", "name": "Dotkernel", - "url": "{{ app.baseUrl }}", + "url": "{{ app.url }}", "logo": { "@type": "ImageObject", - "url": "{{ app.baseUrl ~ asset('images/app/logo.png') }}" + "url": "{{ app.url ~ asset('images/app/logo.png') }}" } } } diff --git a/src/Page/templates/page/about.html.twig b/src/Page/templates/page/about.html.twig index 251e9e80..4181fcea 100644 --- a/src/Page/templates/page/about.html.twig +++ b/src/Page/templates/page/about.html.twig @@ -1,65 +1,297 @@ {% extends '@layout/default.html.twig' %} +{% set routeName = 'page::about' %} +{% set meta = { + title: 'About us | The team behind Dotkernel', + description: 'Dotkernel is built by the dev team at Apidemia - a team that reviews every change together, ' + ~ 'gives its work back to the PHP community under MIT, and puts guardrails around the tools it adopts.', +} %} + {% block title %}About Us{% endblock %} {% block content %} -
-
-

ABOUT US

-

demo page

-

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et - dolore magna aliqua. - Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo - consequat.

+ +
+
+
+ About us · Apidemia +

The team behind Dotkernel

+

+ Dotkernel started as an internal tool for handling complex architectures, built because we needed + it ourselves. We released it under MIT because the PHP community gave us the components it stands + on. Everything here is made by a team that has to live with its own decisions. +

+ + {{ include('@partial/markdown-actions.html.twig', {markdown_url: absolute_url(asset('md-pages/about.md'))}) }} +
+
Team Apidemia
+
License MIT
+
Released continuously since 2018
+
+
+ +
+
+
How a change ships
+
+ +
Proposeplan before code
+
Buildhouse structure
+
Reviewanother pair of eyes
+
QA gatecs · PHPStan · tests
+
Documentfor the next person
+
ReleaseMIT, in public
+
+
+
+
+
+
+
One team, one codebase
+ + + + + + + + + + + + + + + + + +
-
- -
-
-
-
-

- - heading - -

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut - labore et dolore magna aliqua. - Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo - consequat. + + +

+
+
+ How we work +

A team that shares the whole codebase

+

+ Nobody here owns a private corner of the code. Every module follows the same structure, every + handler is named the same way, and every application is assembled from the same explicit wiring - + so any developer on the team can open any part of the platform and know where they are. That is a + deliberate design choice, and it is what makes reviewing each other's work possible rather than + polite. +

+

+ Getting better at this is part of the job, not something we hope happens after hours. Our developers + read the upstream source of the components we depend on, write the documentation the next person + will need, and turn every repeated mistake into a written rule instead of a reprimand. When a review + catches the same thing twice, that is a gap in what we have written down - and we fix the writing. +

+
+ Extending the power of + +
+
+
+
+ +

Every change reviewed by someone else

+
+ +
+ +

One structure across every application

+
+ +
+ +

Learning time counted as real work

+
+ +
+ +

Decisions written down, not remembered

+
+
+
+
+ +
+
+
+ What we care about +

Six things we are not willing to trade

+

None of these are aspirations we printed on a wall. Each one shows up somewhere you can check - + in a repository, in a license, in a config file, or in the way a release is handled.

+
+ +
+ +
+
+
How we work · Teamwork

Better together than alone

+
+

The codebase is the team's, not a collection of territories.

+

+ Work is planned out loud before it is written, reviewed before it is merged, and documented + before it is called done. Consistency is what makes that cheap: because a module in one + application looks like a module in every other, a reviewer spends their attention on the + decision being made rather than on finding their way around.

-
-

- - heading - -

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut - labore et dolore magna aliqua. - Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo - consequat. +

+
+
People · Improvement

Developers who keep growing

+
+

Nobody stays where they started.

+

+ Our developers learn by reading the source of the components they build on, by writing the + guides and reference docs the rest of the team relies on, and by taking the review notes that + come back to them as material rather than criticism. Every breaking change we publish ships with + the article explaining it - writing that explanation is how we make sure we actually understood + the change.

-
-

- - heading - -

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut - labore et dolore magna aliqua. - Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo - consequat. +

+
+
Community · Open source

Giving back to PHP

+
+

MIT licensed, and answered in public.

+

+ Dotkernel stands on Mezzio, Laminas and Doctrine, so our work goes back out under a permissive + license with nothing held behind a paid tier. The team answers questions on our own components + and on the Laminas ones underneath them, publishes the support status of every + dot-* package instead of leaving it to guesswork, and keeps the discussion where + anyone can read it.

+ +
+ +
+
+
Customers · Delivery

Every need you can imagine

+
+

The unglamorous requirement is the one that decides a project.

+

+ Our skeletons come out of real client work, which is why they answer questions nobody asks in a + demo: sharing a database with an application that was there first, running several connections + at once, anonymizing an account instead of deleting the row, hearing about a bug that never + threw an exception. We would rather carry that detail for you than let you discover it in your + second sprint. +

+
+ +
+
+
Tooling · Responsible AI

AI with guardrails on

+
+

Adopted everywhere, trusted nowhere by default.

+

+ AI assistance runs through every part of how we work, under rules we wrote down first. Secrets + are never loaded rather than merely redacted, so credentials and signing keys stay out of the + context window entirely. Sessions start by proposing a plan instead of editing files. Linters + report and never rewrite. A package is never named from memory - it is verified against what is + actually installed. And a human reviews every diff, because accountability does not delegate. +

+ +
+ +
+
+
Craft · Quality

Best product, best tools

+
+

Chosen for what they will still be worth in three years.

+

+ We build on interfaces from the PHP Framework Interop Group so a component from one vendor can + be replaced by another, keep static analysis at a strict level, ship the test suite with the + skeleton rather than promising it later, and stay current with the PHP versions our dependencies + support. When a tool stops being the right answer, we say so - publicly, with a migration path. +

+ +
+
+
+
+ +
+
+
+ In practice +

Where you can check any of this

+
+ +
+
+

The QA gate

+

Coding standards, static analysis at a strict rule level and the test suite all run before a + change is merged - and "make the checker quiet" is never an accepted fix.

+
+
+

Documentation as a deliverable

+

A feature is not finished until the next developer can pick it up from what is written, which is + why docs.dotkernel.org + is maintained alongside the code.

+
+
+

Deprecations, not surprises

+

Endpoints announce their own retirement through response headers, so a client finds out from the + API rather than from a changelog nobody read.

+
+
+

Support status in public

+

Every package publishes where it sits in its lifecycle, so choosing one is never a bet on + whether it is still maintained.

+
+
+

Secrets stay out of the tooling

+

Local configuration, environment files and OAuth keys are unreadable to our AI tooling by + configuration, not by convention.

+
+
+

Supported by JetBrains

+

Our open-source tooling is supported through the + JetBrains open source programme - + one of the ways this work stays sustainable.

+ +
+
+
+
+ +
+
+
+ Open source, in production +

Built by a team that has to live with its own decisions.

+
+
+

Dotkernel is developed and led by the dev team at Apidemia. If you are weighing up + a platform, hiring a team, or just want to argue about middleware, we would rather have the + conversation than the brochure.

+
-
+ {% endblock %} diff --git a/src/Page/templates/page/admin.html.twig b/src/Page/templates/page/admin.html.twig index dbdae45a..f0a6274d 100644 --- a/src/Page/templates/page/admin.html.twig +++ b/src/Page/templates/page/admin.html.twig @@ -48,6 +48,7 @@ Live demo
+ {{ include('@partial/markdown-actions.html.twig', {markdown_url: absolute_url(asset('md-pages/admin.md'))}) }}
PHP version for dotkernel/admin Latest release of dotkernel/admin @@ -62,29 +63,6 @@
-
-
Guarded record table
- - - - - - - - - - - - - - - - - - - -
-
Request lifecycle
@@ -99,6 +77,32 @@
+
+
+
Guarded record table
+ + + + + + + + + + + + + + + + + + + + + +
+
diff --git a/src/Page/templates/page/api.html.twig b/src/Page/templates/page/api.html.twig index 2fcf106c..c255cfde 100644 --- a/src/Page/templates/page/api.html.twig +++ b/src/Page/templates/page/api.html.twig @@ -49,6 +49,7 @@ Live demo
+ {{ include('@partial/markdown-actions.html.twig', {markdown_url: absolute_url(asset('md-pages/api.md'))}) }}
PHP version for dotkernel/api Latest release of dotkernel/api @@ -63,25 +64,6 @@
-
-
One resource, its links
- - - - - - - - - - - - - - - -
-
Request lifecycle @@ -99,6 +81,36 @@
+
+
+
One resource, its links
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
diff --git a/src/Page/templates/page/architecture.html.twig b/src/Page/templates/page/architecture.html.twig index d4355748..cd4857fd 100644 --- a/src/Page/templates/page/architecture.html.twig +++ b/src/Page/templates/page/architecture.html.twig @@ -45,7 +45,7 @@ .lib-node em { font-style: normal; font-size: 10.5px; color: var(--text-dim); } .lib-arrow { height: 12px; margin: 3px 0 3px 20px; border-left: 2px dotted var(--border); } - .applies-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; margin-top: 26px; } + .applies-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; margin-top: 26px; } .applies { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 20px 18px; } .applies h4 { font-family: var(--mono); font-size: 14px; margin: 0 0 8px; } .applies p { margin: 0; font-size: 13.5px; line-height: 1.6; color: var(--text-muted); } @@ -88,6 +88,7 @@ Dotkernel API
+ {{ include('@partial/markdown-actions.html.twig', {markdown_url: absolute_url(asset('md-pages/architecture.md'))}) }}
Contract PSR-7 + PSR-15
Container PSR-11
@@ -96,32 +97,6 @@
-
-
Nineteen stages, folded
- - - - - - - - - - - - - - - - - - - - - - -
-
Five phases19 stages
@@ -136,6 +111,41 @@
+
+
+
Nineteen stages, folded
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
@@ -559,10 +569,10 @@
Across the platform -

One pipeline shape, four applications

-

The stages differ where the job differs - Frontend adds sessions and template rendering, Queue - swaps HTTP dispatch for a message consumer - but the pattern is constant, and all four declare the - same Core namespaces underneath.

+

One pipeline shape, three applications

+

The stages differ where the job differs - Admin adds sessions and CSRF-protected forms, Queue + swaps HTTP dispatch for a message consumer - but the pattern is constant, and all three declare + the same Core namespaces underneath.

@@ -582,10 +592,6 @@

Admin

Same shape, plus sessions, CSRF-protected forms and 2FA in the identity phase.

-
-

Frontend

-

Same shape, ending in server-rendered templates rather than HAL payloads.

-

Queue

The same middleware thinking applied to messages instead of requests.

diff --git a/src/Page/templates/page/dotboost.html.twig b/src/Page/templates/page/dotboost.html.twig index 63b736c5..cb9342df 100644 --- a/src/Page/templates/page/dotboost.html.twig +++ b/src/Page/templates/page/dotboost.html.twig @@ -44,6 +44,7 @@ Claude Code docs
+ {{ include('@partial/markdown-actions.html.twig', {markdown_url: absolute_url(asset('md-pages/dotboost.md'))}) }}
License of dotkernel/dotboost GitHub stars for dotkernel/dotboost @@ -57,24 +58,6 @@
-
-
Loaded into context
- - - - - - - - - - - - - - -
-
What happens in a session
@@ -89,6 +72,28 @@
+
+
+
Loaded into context
+ + + + + + + + + + + + + + + + + +
+
diff --git a/src/Page/templates/page/frontend.html.twig b/src/Page/templates/page/frontend.html.twig index 4c3f6303..ca4a25ad 100644 --- a/src/Page/templates/page/frontend.html.twig +++ b/src/Page/templates/page/frontend.html.twig @@ -47,6 +47,7 @@ Live demo
+ {{ include('@partial/markdown-actions.html.twig', {markdown_url: absolute_url(asset('md-pages/frontend.md'))}) }}
PHP version for dotkernel/frontend Latest release of dotkernel/frontend @@ -61,24 +62,6 @@
-
-
Composed on the server
- - - - - - - - - - - - - - -
-
Request lifecycle
@@ -93,6 +76,26 @@
+
+
+
Composed on the server
+ + + + + + + + + + + + + + + +
+
diff --git a/src/Page/templates/page/light.html.twig b/src/Page/templates/page/light.html.twig index 62c97584..23e8214c 100644 --- a/src/Page/templates/page/light.html.twig +++ b/src/Page/templates/page/light.html.twig @@ -48,6 +48,7 @@ Live demo + {{ include('@partial/markdown-actions.html.twig', {markdown_url: absolute_url(asset('md-pages/light.md'))}) }}
PHP version for dotkernel/light Latest release of dotkernel/light @@ -62,19 +63,6 @@
-
-
Six direct dependencies
- - - - - - - - - -
-
Request lifecycle
@@ -90,6 +78,25 @@
+
+
+
Six direct dependencies
+ + + + + + + + + + + + + + +
+
diff --git a/src/Page/templates/page/queue.html.twig b/src/Page/templates/page/queue.html.twig index b7af54c7..d89dcd5a 100644 --- a/src/Page/templates/page/queue.html.twig +++ b/src/Page/templates/page/queue.html.twig @@ -47,6 +47,7 @@ View on GitHub + {{ include('@partial/markdown-actions.html.twig', {markdown_url: absolute_url(asset('md-pages/queue.md'))}) }}
PHP version for dotkernel/queue Latest release of dotkernel/queue @@ -61,25 +62,6 @@
-
-
Stream, worker, dead letter
- - - - - - - - - - - - - - - -
-
Message lifecycle
@@ -94,6 +76,28 @@
+
+
+
Stream, worker, dead letter
+ + + + + + + + + + + + + + + + + +
+
diff --git a/src/Page/templates/page/who-we-are.html.twig b/src/Page/templates/page/who-we-are.html.twig index 382cc6f7..f4f35bff 100644 --- a/src/Page/templates/page/who-we-are.html.twig +++ b/src/Page/templates/page/who-we-are.html.twig @@ -11,6 +11,7 @@ dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

+ {{ include('@partial/markdown-actions.html.twig', {markdown_url: absolute_url(asset('md-pages/who-we-are.md'))}) }} diff --git a/test/Unit/App/DBAL/Types/UuidTypeTest.php b/test/Unit/App/DBAL/Types/UuidTypeTest.php new file mode 100644 index 00000000..eff94501 --- /dev/null +++ b/test/Unit/App/DBAL/Types/UuidTypeTest.php @@ -0,0 +1,99 @@ +type = new UuidType(); + } + + /** + * The type is registered with Doctrine under this name. + */ + public function testTheTypeNameIsUuid(): void + { + $this->assertSame('uuid', $this->type::NAME); + } + + /** + * Identifiers use the database's native `uuid` column rather than `binary`, on every platform + * that reaches this type - which is why MySQL, having no such type, is unsupported. + * + * @throws Exception + */ + public function testGetSqlDeclarationIsTheNativeUuidColumnRegardlessOfPlatform(): void + { + $this->assertSame('UUID', $this->type->getSQLDeclaration([], $this->createStub(PostgreSQLPlatform::class))); + $this->assertSame('UUID', $this->type->getSQLDeclaration([], $this->createStub(MariaDBPlatform::class))); + } + + /** + * @throws Exception + */ + public function testGetSqlDeclarationIgnoresTheColumnDefinition(): void + { + $this->assertSame( + 'UUID', + $this->type->getSQLDeclaration( + ['length' => 16, 'fixed' => true], + $this->createStub(AbstractPlatform::class) + ) + ); + } + + /** + * @throws Exception + */ + public function testConvertToDatabaseValueSerialisesTheUuidToItsStringForm(): void + { + $uuid = Uuid::fromString('1ef9f1a4-1a2b-6c3d-8e4f-5a6b7c8d9e0f'); + + $this->assertSame( + '1ef9f1a4-1a2b-6c3d-8e4f-5a6b7c8d9e0f', + $this->type->convertToDatabaseValue($uuid, $this->createStub(AbstractPlatform::class)) + ); + } + + /** + * @throws Exception + */ + public function testConvertToPhpValueRebuildsTheUuidFromItsStringForm(): void + { + $value = $this->type->convertToPHPValue( + '1ef9f1a4-1a2b-6c3d-8e4f-5a6b7c8d9e0f', + $this->createStub(AbstractPlatform::class) + ); + + // Ramsey hands back a lazy implementation of the interface, not a Uuid instance. + $this->assertInstanceOf(UuidInterface::class, $value); + $this->assertSame('1ef9f1a4-1a2b-6c3d-8e4f-5a6b7c8d9e0f', $value->toString()); + } + + /** + * @throws Exception + */ + public function testConvertHandlesNullBothWays(): void + { + $platform = $this->createStub(AbstractPlatform::class); + + $this->assertNull($this->type->convertToDatabaseValue(null, $platform)); + $this->assertNull($this->type->convertToPHPValue(null, $platform)); + } +} diff --git a/test/Unit/App/Factory/FeedGeneratorFactoryTest.php b/test/Unit/App/Factory/FeedGeneratorFactoryTest.php new file mode 100644 index 00000000..a39ea4af --- /dev/null +++ b/test/Unit/App/Factory/FeedGeneratorFactoryTest.php @@ -0,0 +1,94 @@ +createContainer([ + 'feed' => ['path' => '/tmp/feed.xml'], + 'application' => [ + 'url' => 'https://example.test', + 'meta' => [ + 'title' => 'Light Blog', + 'description' => 'A description', + 'image' => 'https://example.test/default.png', + ], + ], + ])); + + $this->assertSame('/tmp/feed.xml', $generator->getFeedFile()); + $this->assertSame('https://example.test', $this->readProperty($generator, 'baseUrl')); + $this->assertSame('Light Blog', $this->readProperty($generator, 'title')); + $this->assertSame('A description', $this->readProperty($generator, 'description')); + $this->assertSame('https://example.test/default.png', $this->readProperty($generator, 'image')); + } + + /** + * The URL comes from `application.url`, the site's single URL key. + * + * @throws Exception + */ + public function testInvokeDoesNotReadTheRetiredBaseUrlKey(): void + { + $generator = (new FeedGeneratorFactory())($this->createContainer([ + 'feed' => ['path' => '/tmp/feed.xml'], + 'application' => ['baseUrl' => 'https://stale.test'], + ])); + + $this->assertSame('', $this->readProperty($generator, 'baseUrl')); + } + + /** + * @throws Exception + */ + public function testInvokeFallsBackToEmptyStringsForMissingMetadata(): void + { + $generator = (new FeedGeneratorFactory())($this->createContainer([ + 'feed' => ['path' => '/tmp/feed.xml'], + 'application' => [], + ])); + + $this->assertSame('', $this->readProperty($generator, 'baseUrl')); + $this->assertSame('', $this->readProperty($generator, 'title')); + $this->assertSame('', $this->readProperty($generator, 'description')); + $this->assertSame('', $this->readProperty($generator, 'image')); + } + + /** + * @param array $config + * @throws Exception + */ + private function createContainer(array $config): ContainerInterface + { + $container = $this->createStub(ContainerInterface::class); + $container + ->method('get') + ->willReturnCallback( + fn (string $id): mixed => $id === 'config' + ? $config + : $this->createStub(PostRepository::class) + ); + + return $container; + } + + private function readProperty(FeedGenerator $generator, string $name): mixed + { + return (new ReflectionProperty(FeedGenerator::class, $name))->getValue($generator); + } +} diff --git a/test/Unit/App/Factory/LlmsFullGeneratorFactoryTest.php b/test/Unit/App/Factory/LlmsFullGeneratorFactoryTest.php new file mode 100644 index 00000000..46c0b527 --- /dev/null +++ b/test/Unit/App/Factory/LlmsFullGeneratorFactoryTest.php @@ -0,0 +1,106 @@ +createContainer([ + 'llms' => [ + 'sourceDir' => '/tmp/md-articles', + 'outputFile' => '/tmp/llms-full.txt', + 'pagesDir' => '/tmp/md-pages', + ], + 'application' => ['url' => 'https://example.test'], + ])); + + $this->assertSame('/tmp/llms-full.txt', $generator->getOutputFile()); + $this->assertSame('/tmp/md-articles', $this->readProperty($generator, 'sourceDir')); + $this->assertSame('/tmp/md-pages', $this->readProperty($generator, 'pagesDir')); + $this->assertSame('https://example.test', $this->readProperty($generator, 'baseUrl')); + } + + /** + * The pages directory postdates the rest of the config, so a `local.php` written before it + * must still build a working generator - one that simply leaves the page sections out. + * + * @throws Exception + */ + public function testInvokeLeavesThePagesDirectoryUnsetWhenItIsNotConfigured(): void + { + $generator = (new LlmsFullGeneratorFactory())($this->createContainer([ + 'llms' => [ + 'sourceDir' => '/tmp/md-articles', + 'outputFile' => '/tmp/llms-full.txt', + ], + 'application' => ['url' => 'https://example.test'], + ])); + + $this->assertNull($this->readProperty($generator, 'pagesDir')); + } + + /** + * The application URL is read from `application.url` - the site's single URL key. A config + * without it must not blow up, because the placeholder substitution is optional. + * + * @param array $application + * @throws Exception + */ + #[DataProvider('missingUrlProvider')] + public function testInvokeFallsBackToAnEmptyBaseUrl(array $application): void + { + $generator = (new LlmsFullGeneratorFactory())($this->createContainer([ + 'llms' => [ + 'sourceDir' => '/tmp/md-articles', + 'outputFile' => '/tmp/llms-full.txt', + ], + 'application' => $application, + ])); + + $this->assertSame('', $this->readProperty($generator, 'baseUrl')); + } + + /** + * @return array}> + */ + public static function missingUrlProvider(): array + { + return [ + 'application section is empty' => [[]], + 'url is null' => [['url' => null]], + // `baseUrl` was replaced by `url`; a stale key must not be picked up. + 'only the retired baseUrl key' => [['baseUrl' => 'https://stale.test']], + ]; + } + + /** + * @param array $config + * @throws Exception + */ + private function createContainer(array $config): ContainerInterface + { + $container = $this->createStub(ContainerInterface::class); + $container->method('get')->willReturn($config); + + return $container; + } + + private function readProperty(LlmsFullGenerator $generator, string $name): mixed + { + return (new ReflectionProperty(LlmsFullGenerator::class, $name))->getValue($generator); + } +} diff --git a/test/Unit/App/Factory/SitemapGeneratorFactoryTest.php b/test/Unit/App/Factory/SitemapGeneratorFactoryTest.php new file mode 100644 index 00000000..5dd2d315 --- /dev/null +++ b/test/Unit/App/Factory/SitemapGeneratorFactoryTest.php @@ -0,0 +1,116 @@ +createContainer([ + 'sitemap' => ['path' => '/tmp/sitemap.xml'], + 'application' => ['url' => 'https://example.test'], + 'routes' => ['page' => ['contact' => 'contact', 'api' => 'api']], + ])); + + $this->assertSame('/tmp/sitemap.xml', $generator->getSitemapFile()); + $this->assertSame('https://example.test', $this->readProperty($generator, 'baseUrl')); + $this->assertSame(['contact', 'api'], $this->readProperty($generator, 'pageRoutes')); + } + + /** + * The URL comes from `application.url`, the site's single URL key. + * + * @throws Exception + */ + public function testInvokeDoesNotReadTheRetiredBaseUrlKey(): void + { + $generator = (new SitemapGeneratorFactory())($this->createContainer([ + 'sitemap' => ['path' => '/tmp/sitemap.xml'], + 'application' => ['baseUrl' => 'https://stale.test'], + ])); + + $this->assertSame('', $this->readProperty($generator, 'baseUrl')); + } + + /** + * The route slugs are the keys of every module's route list, which is what the sitemap needs. + * + * @throws Exception + */ + public function testInvokeCollectsRouteSlugsFromEveryModule(): void + { + $generator = (new SitemapGeneratorFactory())($this->createContainer([ + 'sitemap' => ['path' => '/tmp/sitemap.xml'], + 'routes' => [ + 'page' => ['contact' => 'contact', 'api' => 'api'], + 'other' => ['extra' => 'extra'], + ], + ])); + + $this->assertSame(['contact', 'api', 'extra'], $this->readProperty($generator, 'pageRoutes')); + } + + /** + * @throws Exception + */ + public function testInvokeSkipsRouteEntriesThatAreNotArrays(): void + { + $generator = (new SitemapGeneratorFactory())($this->createContainer([ + 'sitemap' => ['path' => '/tmp/sitemap.xml'], + 'routes' => ['page' => ['contact' => 'contact'], 'broken' => 'not an array'], + ])); + + $this->assertSame(['contact'], $this->readProperty($generator, 'pageRoutes')); + } + + /** + * @throws Exception + */ + public function testInvokeYieldsNoRoutesWhenNoneAreConfigured(): void + { + $generator = (new SitemapGeneratorFactory())($this->createContainer([ + 'sitemap' => ['path' => '/tmp/sitemap.xml'], + ])); + + $this->assertSame([], $this->readProperty($generator, 'pageRoutes')); + } + + /** + * @param array $config + * @throws Exception + */ + private function createContainer(array $config): ContainerInterface + { + $container = $this->createStub(ContainerInterface::class); + $container + ->method('get') + ->willReturnCallback(fn (string $id): mixed => match ($id) { + 'config' => $config, + CategoryRepository::class => $this->createStub(CategoryRepository::class), + AuthorRepository::class => $this->createStub(AuthorRepository::class), + default => $this->createStub(PostRepository::class), + }); + + return $container; + } + + private function readProperty(SitemapGenerator $generator, string $name): mixed + { + return (new ReflectionProperty(SitemapGenerator::class, $name))->getValue($generator); + } +} diff --git a/test/Unit/App/Service/LlmsFullGeneratorTest.php b/test/Unit/App/Service/LlmsFullGeneratorTest.php new file mode 100644 index 00000000..50e29db2 --- /dev/null +++ b/test/Unit/App/Service/LlmsFullGeneratorTest.php @@ -0,0 +1,361 @@ +'; + + private string $workDir; + private string $sourceDir; + private string $pagesDir; + private string $outputFile; + + protected function setUp(): void + { + parent::setUp(); + + $this->workDir = sprintf( + '%s%slight-llms-%s', + sys_get_temp_dir(), + DIRECTORY_SEPARATOR, + bin2hex(random_bytes(8)) + ); + + $this->sourceDir = $this->workDir . DIRECTORY_SEPARATOR . 'md-articles'; + $this->pagesDir = $this->workDir . DIRECTORY_SEPARATOR . 'md-pages'; + $this->outputFile = $this->workDir . DIRECTORY_SEPARATOR . 'llms-full.txt'; + + mkdir($this->sourceDir, 0775, true); + mkdir($this->pagesDir, 0775, true); + } + + protected function tearDown(): void + { + $this->removeDirectory($this->workDir); + + parent::tearDown(); + } + + public function testGetOutputFileReturnsTheConfiguredPath(): void + { + $this->assertSame($this->outputFile, $this->createGenerator()->getOutputFile()); + } + + public function testWriteReturnsTheNumberOfSectionsWritten(): void + { + $this->writeIndex(); + $this->writeArticle('news', 'first'); + $this->writeArticle('news', 'second'); + $this->writePage('api'); + + $this->assertSame(4, $this->createGenerator()->write()); + } + + public function testWritePlacesTheIndexFirst(): void + { + $this->writeIndex(); + $this->writeArticle('news', 'a-post'); + + $this->createGenerator()->write(); + + $this->assertSame(['index.md', 'news/a-post.md'], $this->sectionLabels()); + } + + public function testWriteOmitsTheIndexWhenItDoesNotExist(): void + { + $this->writeArticle('news', 'a-post'); + + $this->createGenerator()->write(); + + $this->assertSame(['news/a-post.md'], $this->sectionLabels()); + } + + public function testWriteSortsArticlesByRelativePath(): void + { + $this->writeArticle('zebra', 'last'); + $this->writeArticle('alpha', 'first'); + $this->writeArticle('alpha', 'second'); + + $this->createGenerator()->write(); + + $this->assertSame( + ['alpha/first.md', 'alpha/second.md', 'zebra/last.md'], + $this->sectionLabels() + ); + } + + public function testWriteAppendsPagesAfterTheArticles(): void + { + $this->writeIndex(); + $this->writeArticle('news', 'a-post'); + $this->writePage('api'); + $this->writePage('admin'); + + $this->createGenerator()->write(); + + $this->assertSame( + ['index.md', 'news/a-post.md', 'md-pages/admin.md', 'md-pages/api.md'], + $this->sectionLabels() + ); + } + + public function testWriteLeavesPagesOutWhenNoPagesDirectoryIsConfigured(): void + { + $this->writeArticle('news', 'a-post'); + $this->writePage('api'); + + $this->createGenerator(pagesDir: null)->write(); + + $this->assertSame(['news/a-post.md'], $this->sectionLabels()); + } + + public function testWriteIgnoresNestedFilesUnderThePagesDirectory(): void + { + mkdir($this->pagesDir . DIRECTORY_SEPARATOR . 'nested', 0775, true); + file_put_contents($this->pagesDir . '/nested/deep.md', 'deep'); + $this->writePage('api'); + + $this->createGenerator()->write(); + + $this->assertSame(['md-pages/api.md'], $this->sectionLabels()); + } + + public function testWriteIgnoresNonMarkdownFiles(): void + { + file_put_contents($this->sourceDir . '/index.md', 'index body'); + mkdir($this->sourceDir . DIRECTORY_SEPARATOR . 'news', 0775, true); + file_put_contents($this->sourceDir . '/news/notes.txt', 'not markdown'); + file_put_contents($this->pagesDir . '/notes.txt', 'not markdown'); + + $this->createGenerator()->write(); + + $this->assertSame(['index.md'], $this->sectionLabels()); + } + + public function testWriteWrapsEachSectionInTheSeparatorAndItsLabel(): void + { + $this->writePage('api'); + + $this->createGenerator()->write(); + + $this->assertStringContainsString( + sprintf("%s\n\n%s\n\n", self::SEPARATOR, self::SEPARATOR), + $this->writtenOutput() + ); + } + + public function testWriteSubstitutesThePlaceholderWithTheBaseUrl(): void + { + file_put_contents($this->sourceDir . '/index.md', 'Contact: {{base_url}}/contact/'); + + $this->createGenerator(baseUrl: 'https://example.test')->write(); + + $output = $this->writtenOutput(); + $this->assertStringContainsString('Contact: https://example.test/contact/', $output); + $this->assertStringNotContainsString('{{base_url}}', $output); + } + + public function testWriteSubstitutesThePlaceholderInPagesToo(): void + { + file_put_contents($this->pagesDir . '/api.md', 'Docs: {{base_url}}/api/'); + + $this->createGenerator(baseUrl: 'https://example.test')->write(); + + $this->assertStringContainsString('Docs: https://example.test/api/', $this->writtenOutput()); + } + + public function testWriteTrimsSurroundingWhitespaceFromEachSectionBody(): void + { + file_put_contents($this->pagesDir . '/api.md', "\n\n # Body \n\n\n"); + + $this->createGenerator()->write(); + + $this->assertStringEndsWith("# Body\n\n", $this->writtenOutput()); + } + + public function testWriteSeparatesSectionsByTwoBlankLinesAndEndsWithOne(): void + { + $this->writePage('admin'); + $this->writePage('api'); + + $this->createGenerator()->write(); + + $output = $this->writtenOutput(); + + // Two blank lines between the end of one section body and the next section's separator. + $this->assertStringContainsString("# admin\n\n\n" . self::SEPARATOR, $output); + $this->assertStringEndsWith("\n\n", $output); + } + + public function testWriteOverwritesAnExistingOutputFile(): void + { + file_put_contents($this->outputFile, 'stale content'); + $this->writePage('api'); + + $this->createGenerator()->write(); + + $this->assertStringNotContainsString('stale content', $this->writtenOutput()); + } + + public function testWriteIsIdempotent(): void + { + $this->writeIndex(); + $this->writeArticle('news', 'a-post'); + $this->writePage('api'); + + $generator = $this->createGenerator(); + $generator->write(); + $first = $this->writtenOutput(); + $generator->write(); + + $this->assertSame($first, $this->writtenOutput()); + } + + public function testWriteProducesOnlyTheTrailingBlankLinesWhenThereIsNothingToConcatenate(): void + { + $this->assertSame(0, $this->createGenerator()->write()); + $this->assertSame("\n\n", $this->writtenOutput()); + } + + public function testWriteThrowsWhenTheOutputFileCannotBeWritten(): void + { + $this->writePage('api'); + + $generator = $this->createGenerator( + outputFile: $this->workDir . DIRECTORY_SEPARATOR . 'missing' . DIRECTORY_SEPARATOR . 'out.txt' + ); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Unable to write llms-full.txt.'); + + $this->writeSilencingPhpWarnings($generator); + } + + public function testWriteThrowsWhenASourceFileCannotBeRead(): void + { + $unreadable = $this->pagesDir . DIRECTORY_SEPARATOR . 'api.md'; + file_put_contents($unreadable, 'body'); + if (chmod($unreadable, 0000) === false || is_readable($unreadable)) { + $this->markTestSkipped('Cannot make a file unreadable as this user.'); + } + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Unable to read file: md-pages/api.md'); + + try { + $this->writeSilencingPhpWarnings($this->createGenerator()); + } finally { + chmod($unreadable, 0644); + } + } + + /** + * The failure paths under test make `file_get_contents`/`file_put_contents` emit a PHP warning + * before returning false. That warning is the documented behaviour of the code being exercised, + * not a problem with the test, so it is swallowed to keep the suite output clean. + */ + private function writeSilencingPhpWarnings(LlmsFullGenerator $generator): void + { + set_error_handler(static fn (): bool => true); + + try { + $generator->write(); + } finally { + restore_error_handler(); + } + } + + private function createGenerator( + ?string $pagesDir = '', + string $baseUrl = 'https://example.test', + ?string $outputFile = null, + ): LlmsFullGenerator { + return new LlmsFullGenerator( + $this->sourceDir, + $outputFile ?? $this->outputFile, + $baseUrl, + $pagesDir === '' ? $this->pagesDir : $pagesDir, + ); + } + + private function writeIndex(): void + { + file_put_contents($this->sourceDir . DIRECTORY_SEPARATOR . 'index.md', '# Index'); + } + + private function writeArticle(string $category, string $slug): void + { + $directory = $this->sourceDir . DIRECTORY_SEPARATOR . $category; + if (! is_dir($directory)) { + mkdir($directory, 0775, true); + } + + file_put_contents($directory . DIRECTORY_SEPARATOR . $slug . '.md', sprintf('# %s', $slug)); + } + + private function writePage(string $slug): void + { + file_put_contents($this->pagesDir . DIRECTORY_SEPARATOR . $slug . '.md', sprintf('# %s', $slug)); + } + + private function writtenOutput(): string + { + $contents = file_get_contents($this->outputFile); + $this->assertIsString($contents); + + return $contents; + } + + /** + * The section labels written into the header comments, in the order they appear. + * + * @return list + */ + private function sectionLabels(): array + { + preg_match_all('/^$/m', $this->writtenOutput(), $matches); + + return $matches[1]; + } + + private function removeDirectory(string $directory): void + { + if (! is_dir($directory)) { + return; + } + + foreach (array_diff((array) scandir($directory), ['.', '..']) as $entry) { + $path = $directory . DIRECTORY_SEPARATOR . $entry; + is_dir($path) ? $this->removeDirectory($path) : unlink($path); + } + + rmdir($directory); + } +} diff --git a/test/Unit/Blog/DBAL/Types/PostStatusEnumTypeTest.php b/test/Unit/Blog/DBAL/Types/PostStatusEnumTypeTest.php new file mode 100644 index 00000000..36a2a297 --- /dev/null +++ b/test/Unit/Blog/DBAL/Types/PostStatusEnumTypeTest.php @@ -0,0 +1,138 @@ +type = new PostStatusEnumType(); + } + + public function testGetNameReturnsTheTypeConstant(): void + { + $this->assertSame('post_status_enum', $this->type->getName()); + $this->assertSame(PostStatusEnumType::NAME, $this->type->getName()); + } + + public function testGetEnumClassReturnsTheBackingEnum(): void + { + $this->assertSame(PostStatusEnum::class, $this->type->getEnumClass()); + } + + public function testGetEnumCasesReturnsEveryCaseOfTheBackingEnum(): void + { + $this->assertSame(PostStatusEnum::cases(), $this->type->getEnumCases()); + } + + public function testGetEnumValuesReturnsEveryCaseValue(): void + { + $this->assertSame(['draft', 'published', 'private', 'archived'], $this->type->getEnumValues()); + } + + /** + * PostgreSQL declares a named enum type of its own, so the column reuses that name. + * + * @throws Exception + */ + public function testGetSqlDeclarationUsesTheTypeNameOnPostgres(): void + { + $this->assertSame( + 'post_status_enum', + $this->type->getSQLDeclaration([], $this->createStub(PostgreSQLPlatform::class)) + ); + } + + /** + * SQLite has no enum type - the test suite runs on it, so this branch matters. + * + * @throws Exception + */ + public function testGetSqlDeclarationFallsBackToTextOnSqlite(): void + { + $this->assertSame( + 'TEXT', + $this->type->getSQLDeclaration([], $this->createStub(SQLitePlatform::class)) + ); + } + + /** + * @throws Exception + */ + public function testGetSqlDeclarationListsEveryValueOnPlatformsWithNativeEnums(): void + { + $this->assertSame( + "ENUM('draft', 'published', 'private', 'archived')", + $this->type->getSQLDeclaration([], $this->createStub(MariaDBPlatform::class)) + ); + } + + /** + * @throws Exception + */ + public function testConvertToDatabaseValueUnwrapsTheEnumToItsBackingValue(): void + { + $this->assertSame( + 'published', + $this->type->convertToDatabaseValue(PostStatusEnum::Published, $this->platform()) + ); + } + + /** + * @throws Exception + */ + public function testConvertToPhpValueUnwrapsTheEnumToItsBackingValue(): void + { + $this->assertSame( + 'draft', + $this->type->convertToPHPValue(PostStatusEnum::Draft, $this->platform()) + ); + } + + /** + * Rows written before the type existed hold plain strings, and null is a valid column value. + * + * @throws Exception + */ + public function testConvertPassesThroughValuesThatAreNotEnums(): void + { + $platform = $this->platform(); + + $this->assertSame('published', $this->type->convertToDatabaseValue('published', $platform)); + $this->assertNull($this->type->convertToDatabaseValue(null, $platform)); + $this->assertSame('published', $this->type->convertToPHPValue('published', $platform)); + $this->assertNull($this->type->convertToPHPValue(null, $platform)); + } + + /** + * @throws Exception + */ + public function testGetValueReturnsTheBackingValueOfAnEnumAndLeavesAnythingElseAlone(): void + { + $this->assertSame('archived', $this->type->getValue(PostStatusEnum::Archived)); + $this->assertSame(42, $this->type->getValue(42)); + } + + /** + * @throws Exception + */ + private function platform(): AbstractPlatform + { + return $this->createStub(AbstractPlatform::class); + } +} diff --git a/test/Unit/Blog/Enum/PostStatusEnumTest.php b/test/Unit/Blog/Enum/PostStatusEnumTest.php new file mode 100644 index 00000000..108d8b25 --- /dev/null +++ b/test/Unit/Blog/Enum/PostStatusEnumTest.php @@ -0,0 +1,34 @@ +assertSame(['draft', 'published', 'private', 'archived'], PostStatusEnum::values()); + } + + public function testEachCaseIsBackedByItsLowercaseName(): void + { + $this->assertSame('draft', PostStatusEnum::Draft->value); + $this->assertSame('published', PostStatusEnum::Published->value); + $this->assertSame('private', PostStatusEnum::Private->value); + $this->assertSame('archived', PostStatusEnum::Archived->value); + } + + public function testFromResolvesAStoredValueBackToItsCase(): void + { + $this->assertSame(PostStatusEnum::Published, PostStatusEnum::from('published')); + } + + public function testTryFromReturnsNullForAValueThatIsNotACase(): void + { + $this->assertNull(PostStatusEnum::tryFrom('publish')); + } +} diff --git a/test/Unit/Page/Factory/GetPageViewHandlerFactoryTest.php b/test/Unit/Page/Factory/GetPageViewHandlerFactoryTest.php new file mode 100644 index 00000000..8cfde7e8 --- /dev/null +++ b/test/Unit/Page/Factory/GetPageViewHandlerFactoryTest.php @@ -0,0 +1,62 @@ +createStub(TemplateRendererInterface::class); + $categoryRepository = $this->createStub(CategoryRepository::class); + $postRepository = $this->createStub(PostRepository::class); + + $handler = (new GetPageViewHandlerFactory())( + $this->createContainer($template, $categoryRepository, $postRepository), + GetPageViewHandler::class + ); + + $this->assertSame($template, $this->readProperty($handler, 'template')); + $this->assertSame($categoryRepository, $this->readProperty($handler, 'categoryRepository')); + $this->assertSame($postRepository, $this->readProperty($handler, 'postRepository')); + } + + /** + * @throws Exception + */ + private function createContainer( + TemplateRendererInterface $template, + CategoryRepository $categoryRepository, + PostRepository $postRepository, + ): ContainerInterface { + $container = $this->createStub(ContainerInterface::class); + $container + ->method('get') + ->willReturnCallback(fn (string $id): mixed => match ($id) { + TemplateRendererInterface::class => $template, + CategoryRepository::class => $categoryRepository, + default => $postRepository, + }); + + return $container; + } + + private function readProperty(GetPageViewHandler $handler, string $name): mixed + { + return (new ReflectionProperty(GetPageViewHandler::class, $name))->getValue($handler); + } +} diff --git a/test/Unit/Page/Factory/PageServiceFactoryTest.php b/test/Unit/Page/Factory/PageServiceFactoryTest.php new file mode 100644 index 00000000..034790b5 --- /dev/null +++ b/test/Unit/Page/Factory/PageServiceFactoryTest.php @@ -0,0 +1,42 @@ +createStub(ContainerInterface::class), + PageService::class + ); + + // The declared return type is the interface; what matters is which implementation it builds. + $this->assertInstanceOf(PageService::class, $service); + } + + /** + * @throws Exception + */ + public function testInvokeBuildsAFreshServiceEachTime(): void + { + $factory = new PageServiceFactory(); + $container = $this->createStub(ContainerInterface::class); + + $this->assertNotSame( + $factory($container, PageService::class), + $factory($container, PageService::class) + ); + } +} diff --git a/test/Unit/Page/Handler/GetPageViewHandlerTest.php b/test/Unit/Page/Handler/GetPageViewHandlerTest.php new file mode 100644 index 00000000..66cc98ad --- /dev/null +++ b/test/Unit/Page/Handler/GetPageViewHandlerTest.php @@ -0,0 +1,138 @@ +createMock(TemplateRendererInterface::class); + $template + ->expects($this->once()) + ->method('render') + ->with('page::api', $this->anything()) + ->willReturn('

Dotkernel API

'); + + $response = $this->createHandler($template)->handle($this->createRequest('page::api')); + + $this->assertSame(StatusCodeInterface::STATUS_OK, $response->getStatusCode()); + $this->assertSame('

Dotkernel API

', (string) $response->getBody()); + } + + /** + * @throws Exception + */ + public function testHandleReturnsAnHtmlResponse(): void + { + $response = $this->createHandler()->handle($this->createRequest('page::api')); + + $this->assertStringContainsString('text/html', $response->getHeaderLine('Content-Type')); + } + + /** + * @throws Exception + */ + public function testHandlePassesTheThreeMostRecentPostsToTheTemplate(): void + { + $posts = [new Post(), new Post(), new Post()]; + + $postRepository = $this->createMock(PostRepository::class); + $postRepository + ->expects($this->once()) + ->method('getRecentPosts') + ->with(3) + ->willReturn($posts); + + $template = $this->createMock(TemplateRendererInterface::class); + $template + ->expects($this->once()) + ->method('render') + ->with('page::light', $this->callback( + fn (array $params): bool => $params['posts'] === $posts + )) + ->willReturn(''); + + $this->createHandler($template, postRepository: $postRepository) + ->handle($this->createRequest('page::light')); + } + + /** + * @throws Exception + */ + public function testHandlePassesTheCategoriesToTheTemplate(): void + { + $categories = [new Category()]; + + $categoryRepository = $this->createMock(CategoryRepository::class); + $categoryRepository + ->expects($this->once()) + ->method('getCategories') + ->willReturn($categories); + + $template = $this->createMock(TemplateRendererInterface::class); + $template + ->expects($this->once()) + ->method('render') + ->with('page::queue', $this->callback( + fn (array $params): bool => $params['categories'] === $categories + )) + ->willReturn(''); + + $this->createHandler($template, categoryRepository: $categoryRepository) + ->handle($this->createRequest('page::queue')); + } + + /** + * @throws Exception + */ + private function createHandler( + ?TemplateRendererInterface $template = null, + ?CategoryRepository $categoryRepository = null, + ?PostRepository $postRepository = null, + ): GetPageViewHandler { + if (! $template instanceof TemplateRendererInterface) { + $template = $this->createStub(TemplateRendererInterface::class); + $template->method('render')->willReturn(''); + } + + return new GetPageViewHandler( + $template, + $categoryRepository ?? $this->createStub(CategoryRepository::class), + $postRepository ?? $this->createStub(PostRepository::class), + ); + } + + /** + * @throws Exception + */ + private function createRequest(string $routeName): ServerRequestInterface + { + $routeResult = $this->createStub(RouteResult::class); + $routeResult->method('getMatchedRouteName')->willReturn($routeName); + + $request = $this->createStub(ServerRequestInterface::class); + $request->method('getAttribute')->willReturn($routeResult); + + return $request; + } +}