tina4ruby 3.13.81 → 3.13.82

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- *,*::before,*::after{box-sizing:border-box}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;scroll-behavior:smooth}body{margin:0;font-family:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:0.5rem;font-weight:500;line-height:1.25}p{margin-top:0;margin-bottom:1rem}ol,ul{padding-left:2rem;margin-top:0;margin-bottom:1rem}a{color:#4a90d9;text-decoration:none}a:hover{color:#256ab1;text-decoration:underline}img,svg{max-width:100%;height:auto;vertical-align:middle}table{border-collapse:collapse;caption-side:bottom}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}button,[type="button"],[type="reset"],[type="submit"]{-webkit-appearance:button;cursor:pointer}hr{margin:1rem 0;color:inherit;border:0;border-top:1px solid rgba(0,0,0,0.1);opacity:0.25}pre,code,kbd,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}[hidden]{display:none !important}h1{font-size:2.25rem}h2{font-size:1.875rem}h3{font-size:1.5rem}h4{font-size:1.25rem}h5{font-size:1.125rem}h6{font-size:1rem}small,.small{font-size:0.875em}mark,.mark{padding:0.2em;background-color:rgba(255,193,7,0.3)}blockquote{margin:0 0 1rem;padding:0.5rem 1rem;border-left:0.25rem solid rgba(0,0,0,0.1);font-size:1.125rem}code{font-size:0.875em;color:#dc3545;word-wrap:break-word}pre{display:block;padding:1rem;font-size:0.875em;color:#212529;background-color:#f8f9fa;border-radius:.25rem}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:0.5rem}.container,.container-fluid{width:100%;padding-right:calc($grid-gutter / 2);padding-left:calc($grid-gutter / 2);margin-right:auto;margin-left:auto}@media (min-width: 576px){.container{max-width:540px}}@media (min-width: 768px){.container{max-width:720px}}@media (min-width: 992px){.container{max-width:960px}}@media (min-width: 1200px){.container{max-width:1140px}}@media (min-width: 1400px){.container{max-width:1320px}}.row{display:flex;flex-wrap:wrap;margin-right:calc($grid-gutter / -2);margin-left:calc($grid-gutter / -2)}.row>*{flex-shrink:0;width:100%;max-width:100%;padding-right:calc($grid-gutter / 2);padding-left:calc($grid-gutter / 2)}.col{flex:1 0 0%}.col-1{flex:0 0 auto;width:8.33333%}.col-2{flex:0 0 auto;width:16.66667%}.col-3{flex:0 0 auto;width:25%}.col-4{flex:0 0 auto;width:33.33333%}.col-5{flex:0 0 auto;width:41.66667%}.col-6{flex:0 0 auto;width:50%}.col-7{flex:0 0 auto;width:58.33333%}.col-8{flex:0 0 auto;width:66.66667%}.col-9{flex:0 0 auto;width:75%}.col-10{flex:0 0 auto;width:83.33333%}.col-11{flex:0 0 auto;width:91.66667%}.col-12{flex:0 0 auto;width:100%}.offset-1{margin-left:8.33333%}.offset-2{margin-left:16.66667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333%}.offset-0{margin-left:0}@media (min-width: 576px){.col-sm{flex:1 0 0%}.col-sm-1{flex:0 0 auto;width:8.33333%}.col-sm-2{flex:0 0 auto;width:16.66667%}.col-sm-3{flex:0 0 auto;width:25%}.col-sm-4{flex:0 0 auto;width:33.33333%}.col-sm-5{flex:0 0 auto;width:41.66667%}.col-sm-6{flex:0 0 auto;width:50%}.col-sm-7{flex:0 0 auto;width:58.33333%}.col-sm-8{flex:0 0 auto;width:66.66667%}.col-sm-9{flex:0 0 auto;width:75%}.col-sm-10{flex:0 0 auto;width:83.33333%}.col-sm-11{flex:0 0 auto;width:91.66667%}.col-sm-12{flex:0 0 auto;width:100%}.offset-sm-0{margin-left:0}}@media (min-width: 768px){.col-md{flex:1 0 0%}.col-md-1{flex:0 0 auto;width:8.33333%}.col-md-2{flex:0 0 auto;width:16.66667%}.col-md-3{flex:0 0 auto;width:25%}.col-md-4{flex:0 0 auto;width:33.33333%}.col-md-5{flex:0 0 auto;width:41.66667%}.col-md-6{flex:0 0 auto;width:50%}.col-md-7{flex:0 0 auto;width:58.33333%}.col-md-8{flex:0 0 auto;width:66.66667%}.col-md-9{flex:0 0 auto;width:75%}.col-md-10{flex:0 0 auto;width:83.33333%}.col-md-11{flex:0 0 auto;width:91.66667%}.col-md-12{flex:0 0 auto;width:100%}.offset-md-0{margin-left:0}}@media (min-width: 992px){.col-lg{flex:1 0 0%}.col-lg-1{flex:0 0 auto;width:8.33333%}.col-lg-2{flex:0 0 auto;width:16.66667%}.col-lg-3{flex:0 0 auto;width:25%}.col-lg-4{flex:0 0 auto;width:33.33333%}.col-lg-5{flex:0 0 auto;width:41.66667%}.col-lg-6{flex:0 0 auto;width:50%}.col-lg-7{flex:0 0 auto;width:58.33333%}.col-lg-8{flex:0 0 auto;width:66.66667%}.col-lg-9{flex:0 0 auto;width:75%}.col-lg-10{flex:0 0 auto;width:83.33333%}.col-lg-11{flex:0 0 auto;width:91.66667%}.col-lg-12{flex:0 0 auto;width:100%}.offset-lg-0{margin-left:0}}@media (min-width: 1200px){.col-xl{flex:1 0 0%}.col-xl-1{flex:0 0 auto;width:8.33333%}.col-xl-2{flex:0 0 auto;width:16.66667%}.col-xl-3{flex:0 0 auto;width:25%}.col-xl-4{flex:0 0 auto;width:33.33333%}.col-xl-5{flex:0 0 auto;width:41.66667%}.col-xl-6{flex:0 0 auto;width:50%}.col-xl-7{flex:0 0 auto;width:58.33333%}.col-xl-8{flex:0 0 auto;width:66.66667%}.col-xl-9{flex:0 0 auto;width:75%}.col-xl-10{flex:0 0 auto;width:83.33333%}.col-xl-11{flex:0 0 auto;width:91.66667%}.col-xl-12{flex:0 0 auto;width:100%}.offset-xl-0{margin-left:0}}.justify-start{justify-content:flex-start}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-between{justify-content:space-between}.justify-around{justify-content:space-around}.align-start{align-items:flex-start}.align-center{align-items:center}.align-end{align-items:flex-end}.btn{display:inline-block;font-weight:400;line-height:1.5;color:#212529;text-align:center;vertical-align:middle;cursor:pointer;user-select:none;background-color:transparent;border:1px solid transparent;padding:0.375rem 0.75rem;font-size:1rem;border-radius:.25rem;transition:color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;text-decoration:none}.btn:disabled,.btn.disabled{pointer-events:none;opacity:0.65}.btn-primary{color:#fff;background-color:#4a90d9;border-color:#4a90d9}.btn-primary:hover{background-color:#2b7bcf;border-color:#2a76c6}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{background-color:#596167;border-color:#545b62}.btn-success{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:hover{background-color:#208637;border-color:#1e7e34}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{background-color:#c62232;border-color:#bd2130}.btn-warning{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{background-color:#dda600;border-color:#d39e00}.btn-info{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:hover{background-color:#128294;border-color:#117a8b}.btn-light{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{background-color:#e0e5e9;border-color:#dae0e5}.btn-dark{color:#fff;background-color:#212529;border-color:#212529}.btn-dark:hover{background-color:#0f1112;border-color:#0a0c0d}.btn-outline-primary{color:#4a90d9;border-color:#4a90d9;background-color:transparent}.btn-outline-primary:hover{color:#fff;background-color:#4a90d9}.btn-outline-secondary{color:#6c757d;border-color:#6c757d;background-color:transparent}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d}.btn-outline-success{color:#28a745;border-color:#28a745;background-color:transparent}.btn-outline-success:hover{color:#fff;background-color:#28a745}.btn-outline-danger{color:#dc3545;border-color:#dc3545;background-color:transparent}.btn-outline-danger:hover{color:#fff;background-color:#dc3545}.btn-outline-warning{color:#ffc107;border-color:#ffc107;background-color:transparent}.btn-outline-warning:hover{color:#212529;background-color:#ffc107}.btn-outline-info{color:#17a2b8;border-color:#17a2b8;background-color:transparent}.btn-outline-info:hover{color:#fff;background-color:#17a2b8}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa;background-color:transparent}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa}.btn-outline-dark{color:#212529;border-color:#212529;background-color:transparent}.btn-outline-dark:hover{color:#fff;background-color:#212529}.btn-sm{padding:0.25rem 0.5rem;font-size:.875rem;border-radius:.25rem}.btn-lg{padding:0.5rem 1rem;font-size:1.125rem;border-radius:.5rem}.btn-block{display:block;width:100%}.form-group{margin-bottom:1rem}.form-label{display:inline-block;margin-bottom:0.5rem;font-weight:700}.form-control{display:block;width:100%;padding:0.375rem 0.75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;appearance:none}.form-control:focus{color:#212529;background-color:#fff;border-color:#b3d1ef;outline:0;box-shadow:0 0 0 0.2rem rgba(74,144,217,0.25)}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled{background-color:#f8f9fa;opacity:1}textarea.form-control{min-height:calc(1.5em + 0.75rem + 2px)}.form-select{display:block;width:100%;padding:0.375rem 2.25rem 0.375rem 0.75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem;transition:border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;appearance:none}.form-select:focus{border-color:#b3d1ef;outline:0;box-shadow:0 0 0 0.2rem rgba(74,144,217,0.25)}.form-check{display:block;min-height:1.5rem;padding-left:1.5em;margin-bottom:0.125rem}.form-check-input{float:left;width:1em;height:1em;margin-left:-1.5em;margin-top:0.25em;appearance:none;background-color:#fff;border:1px solid rgba(0,0,0,0.25)}.form-check-input[type="checkbox"]{border-radius:0.25em}.form-check-input[type="radio"]{border-radius:50%}.form-check-input:checked{background-color:#4a90d9;border-color:#4a90d9}.form-check-input:focus{border-color:#b3d1ef;outline:0;box-shadow:0 0 0 0.2rem rgba(74,144,217,0.25)}.form-check-label{cursor:pointer}.is-valid{border-color:#28a745 !important}.is-valid:focus{box-shadow:0 0 0 0.2rem rgba(40,167,69,0.25) !important}.is-invalid{border-color:#dc3545 !important}.is-invalid:focus{box-shadow:0 0 0 0.2rem rgba(220,53,69,0.25) !important}.valid-feedback,.invalid-feedback{display:none;width:100%;margin-top:0.25rem;font-size:0.875em}.valid-feedback{color:#28a745}.invalid-feedback{color:#dc3545}.is-valid~.valid-feedback,.is-invalid~.invalid-feedback{display:block}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-select{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group-text{display:flex;align-items:center;padding:0.375rem 0.75rem;font-size:1rem;line-height:1.5;color:#212529;text-align:center;white-space:nowrap;background-color:#f8f9fa;border:1px solid #ced4da;border-radius:.25rem}.card{position:relative;display:flex;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,0.125);border-radius:.5rem}.card-header{padding:0.75rem 1rem;margin-bottom:0;background-color:rgba(0,0,0,0.03);border-bottom:1px solid rgba(0,0,0,0.125)}.card-header:first-child{border-radius:calc($border-radius-lg - 1px) calc($border-radius-lg - 1px) 0 0}.card-body{flex:1 1 auto;padding:1rem}.card-footer{padding:0.75rem 1rem;background-color:rgba(0,0,0,0.03);border-top:1px solid rgba(0,0,0,0.125)}.card-footer:last-child{border-radius:0 0 calc($border-radius-lg - 1px) calc($border-radius-lg - 1px)}.card-title{margin-bottom:0.5rem;font-weight:700}.card-text:last-child{margin-bottom:0}.card-img-top{width:100%;border-top-left-radius:calc($border-radius-lg - 1px);border-top-right-radius:calc($border-radius-lg - 1px)}.nav{display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:0.5rem 1rem;color:#4a90d9;text-decoration:none;transition:color 0.15s ease-in-out, background-color 0.15s ease-in-out}.nav-link:hover,.nav-link:focus{color:#256ab1}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-link.active{color:#4a90d9;font-weight:700}.navbar{position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding:0.5rem 1rem}.navbar-brand{padding-top:0.3125rem;padding-bottom:0.3125rem;margin-right:1rem;font-size:1.25rem;font-weight:700;color:inherit;text-decoration:none;white-space:nowrap}.navbar-brand:hover{color:inherit;opacity:0.8}.navbar-nav{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0.5rem;padding-left:0.5rem}.navbar-toggler{padding:0.25rem 0.75rem;font-size:1.125rem;line-height:1;background-color:transparent;border:1px solid rgba(0,0,0,0.1);border-radius:.25rem;cursor:pointer}.navbar-toggler:focus{outline:0;box-shadow:0 0 0 0.2rem rgba(74,144,217,0.25)}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center;display:none}.navbar-collapse.show{display:flex}@media (min-width: 576px){.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-collapse{display:flex;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (min-width: 768px){.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-collapse{display:flex;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (min-width: 992px){.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-collapse{display:flex;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (min-width: 1200px){.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-collapse{display:flex;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}@media (min-width: 1400px){.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-collapse{display:flex;flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}}@media (min-width: 992px){.navbar-nav{flex-direction:row}.navbar-collapse{display:flex;flex-basis:auto}.navbar-toggler{display:none}}.navbar-dark{color:#fff;background-color:#212529}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .nav-link{color:rgba(255,255,255,0.75)}.navbar-dark .nav-link:hover,.navbar-dark .nav-link.active{color:#fff}.navbar-light{background-color:#f8f9fa}.navbar-light .nav-link{color:rgba(0,0,0,0.55)}.navbar-light .nav-link:hover,.navbar-light .nav-link.active{color:rgba(0,0,0,0.9)}.breadcrumb{display:flex;flex-wrap:wrap;padding:0.5rem 1rem;margin-bottom:1rem;list-style:none;background-color:#f8f9fa;border-radius:.25rem}.breadcrumb-item+.breadcrumb-item::before{display:inline-block;padding-right:0.5rem;padding-left:0.5rem;color:#6c757d;content:"/"}.breadcrumb-item.active{color:#6c757d}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal.show{display:block}.modal-dialog{position:relative;width:auto;margin:1.75rem auto;max-width:500px}.modal-sm{max-width:300px}.modal-lg{max-width:800px}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,0.2);border-radius:.5rem;box-shadow:0 0.5rem 1rem rgba(0,0,0,0.15);outline:0}.modal-header{display:flex;flex-shrink:0;align-items:center;justify-content:space-between;padding:1rem;border-bottom:1px solid rgba(0,0,0,0.1)}.modal-title{margin-bottom:0;font-weight:700}.modal-body{position:relative;flex:1 1 auto;padding:1rem}.modal-footer{display:flex;flex-shrink:0;flex-wrap:wrap;align-items:center;justify-content:flex-end;padding:0.75rem;border-top:1px solid rgba(0,0,0,0.1);gap:0.5rem}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:rgba(0,0,0,0.5);display:none}.modal-backdrop.show{display:block}.alert{position:relative;padding:0.75rem 1rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-primary{color:#1c5187;background-color:#deeaf8;border-color:#b3d1ef}.alert-secondary{color:#313539;background-color:#caced1;border-color:#afb5ba}.alert-success{color:#0f401b;background-color:#9be7ac;border-color:#71dd8a}.alert-danger{color:#7c151f;background-color:#f6cdd1;border-color:#efa2a9}.alert-warning{color:#876500;background-color:#ffeeba;border-color:#ffe187}.alert-info{color:#093e47;background-color:#90e4f1;border-color:#63d9ec}.alert-light{color:#aeb9c5;background-color:#fff;border-color:#fff}.alert-dark{color:#000;background-color:#717e8c;border-color:#5a6570}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;padding:0.9375rem 1rem;background:transparent;border:0;cursor:pointer;color:inherit;opacity:0.5}.alert-dismissible .btn-close:hover{opacity:0.75}.table{width:100%;margin-bottom:1rem;vertical-align:top;border-color:rgba(0,0,0,0.1)}.table>:not(caption)>*>*{padding:0.5rem;border-bottom-width:1px;border-bottom-style:solid;border-bottom-color:inherit}.table>thead{vertical-align:bottom;border-bottom:2px solid currentColor}.table>tbody>tr:last-child>*{border-bottom-color:transparent}.table-sm>:not(caption)>*>*{padding:0.25rem}.table-bordered{border:1px solid rgba(0,0,0,0.1)}.table-bordered>:not(caption)>*>*{border-width:1px;border-style:solid;border-color:inherit}.table-striped>tbody>tr:nth-of-type(odd)>*{background-color:rgba(0,0,0,0.02)}.table-hover>tbody>tr:hover>*{background-color:rgba(0,0,0,0.04)}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}.badge{display:inline-block;padding:0.25em 0.65em;font-size:0.75em;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:50rem}.badge-primary{color:#fff;background-color:#4a90d9}.badge-secondary{color:#fff;background-color:#6c757d}.badge-success{color:#fff;background-color:#28a745}.badge-danger{color:#fff;background-color:#dc3545}.badge-warning{color:#212529;background-color:#ffc107}.badge-info{color:#fff;background-color:#17a2b8}.badge-light{color:#212529;background-color:#f8f9fa}.badge-dark{color:#fff;background-color:#212529}.pagination{display:flex;flex-wrap:wrap;padding-left:0;list-style:none;gap:0.25rem}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;background-color:#fff;border-color:rgba(0,0,0,0.1)}.page-item.active .page-link{color:#fff;background-color:#4a90d9;border-color:#4a90d9}.page-link{display:block;padding:0.375rem 0.75rem;color:#4a90d9;background-color:#fff;border:1px solid rgba(0,0,0,0.15);border-radius:.25rem;text-decoration:none;line-height:1.25;transition:color 0.15s, background-color 0.15s, border-color 0.15s}.page-link:hover{color:#2a76c6;background-color:rgba(74,144,217,0.08);border-color:rgba(0,0,0,0.2)}.page-link:focus{outline:0;box-shadow:0 0 0 0.2rem rgba(74,144,217,0.25)}.crud-img-preview{max-height:200px;max-width:100%;border-radius:.25rem;object-fit:cover}.crud-img-preview-sm{max-height:180px;max-width:100%;border-radius:.25rem;object-fit:cover}.d-none{display:none !important}.d-block{display:block !important}.d-flex{display:flex !important}.d-inline{display:inline !important}.d-inline-block{display:inline-block !important}.d-grid{display:grid !important}@media (min-width: 576px){.d-sm-none{display:none !important}.d-sm-block{display:block !important}.d-sm-flex{display:flex !important}.d-sm-inline{display:inline !important}.d-sm-inline-block{display:inline-block !important}}@media (min-width: 768px){.d-md-none{display:none !important}.d-md-block{display:block !important}.d-md-flex{display:flex !important}.d-md-inline{display:inline !important}.d-md-inline-block{display:inline-block !important}}@media (min-width: 992px){.d-lg-none{display:none !important}.d-lg-block{display:block !important}.d-lg-flex{display:flex !important}.d-lg-inline{display:inline !important}.d-lg-inline-block{display:inline-block !important}}.flex-row{flex-direction:row !important}.flex-column{flex-direction:column !important}.flex-wrap{flex-wrap:wrap !important}.flex-nowrap{flex-wrap:nowrap !important}.flex-grow-0{flex-grow:0 !important}.flex-grow-1{flex-grow:1 !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}.align-items-start{align-items:flex-start !important}.align-items-end{align-items:flex-end !important}.align-items-center{align-items:center !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}.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}.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}.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}.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}.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}.my-0{margin-top:0 !important;margin-bottom:0 !important}.mx-0{margin-left:0 !important;margin-right:0 !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.mx-1{margin-left:.25rem !important;margin-right:.25rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.mx-2{margin-left:.5rem !important;margin-right:.5rem !important}.my-3{margin-top:1rem !important;margin-bottom:1rem !important}.mx-3{margin-left:1rem !important;margin-right:1rem !important}.my-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.mx-4{margin-left:1.5rem !important;margin-right:1.5rem !important}.my-5{margin-top:3rem !important;margin-bottom:3rem !important}.mx-5{margin-left:3rem !important;margin-right:3rem !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}.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}.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}.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}.py-0{padding-top:0 !important;padding-bottom:0 !important}.px-0{padding-left:0 !important;padding-right:0 !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.px-1{padding-left:.25rem !important;padding-right:.25rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.px-2{padding-left:.5rem !important;padding-right:.5rem !important}.py-3{padding-top:1rem !important;padding-bottom:1rem !important}.px-3{padding-left:1rem !important;padding-right:1rem !important}.py-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.px-4{padding-left:1.5rem !important;padding-right:1.5rem !important}.py-5{padding-top:3rem !important;padding-bottom:3rem !important}.px-5{padding-left:3rem !important;padding-right:3rem !important}.mx-auto{margin-left:auto !important;margin-right:auto !important}.text-start{text-align:left !important}.text-center{text-align:center !important}.text-end{text-align:right !important}.text-uppercase{text-transform:uppercase !important}.text-capitalize{text-transform:capitalize !important}.text-nowrap{white-space:nowrap !important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fw-bold{font-weight:700 !important}.fw-normal{font-weight:400 !important}.fw-light{font-weight:300 !important}.fs-1{font-size:2.25rem !important}.fs-2{font-size:1.875rem !important}.fs-3{font-size:1.5rem !important}.fs-4{font-size:1.25rem !important}.fs-5{font-size:1rem !important}.fs-6{font-size:.875rem !important}.text-primary{color:#4a90d9 !important}.text-secondary{color:#6c757d !important}.text-success{color:#28a745 !important}.text-danger{color:#dc3545 !important}.text-warning{color:#ffc107 !important}.text-info{color:#17a2b8 !important}.text-light{color:#f8f9fa !important}.text-dark{color:#212529 !important}.text-muted{color:#6c757d !important}.text-white{color:#fff !important}.bg-primary{background-color:#4a90d9 !important}.bg-secondary{background-color:#6c757d !important}.bg-success{background-color:#28a745 !important}.bg-danger{background-color:#dc3545 !important}.bg-warning{background-color:#ffc107 !important}.bg-info{background-color:#17a2b8 !important}.bg-light{background-color:#f8f9fa !important}.bg-dark{background-color:#212529 !important}.bg-white{background-color:#fff !important}.border{border:1px solid rgba(0,0,0,0.1) !important}.border-0{border:0 !important}.rounded{border-radius:.25rem !important}.rounded-0{border-radius:0 !important}.rounded-circle{border-radius:50% !important}.rounded-pill{border-radius:50rem !important}.w-25{width:25% !important}.h-25{height:25% !important}.w-50{width:50% !important}.h-50{height:50% !important}.w-75{width:75% !important}.h-75{height:75% !important}.w-100{width:100% !important}.h-100{height:100% !important}.mw-100{max-width:100% !important}.mh-100{max-height:100% !important}.position-relative{position:relative !important}.position-absolute{position:absolute !important}.position-fixed{position:fixed !important}.position-sticky{position:sticky !important}.position-static{position:static !important}.visible{visibility:visible !important}.invisible{visibility:hidden !important}.overflow-hidden{overflow:hidden !important}.overflow-auto{overflow:auto !important}.shadow{box-shadow:0 0.5rem 1rem rgba(0,0,0,0.15) !important}.shadow-sm{box-shadow:0 0.125rem 0.25rem rgba(0,0,0,0.075) !important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,0.175) !important}.shadow-none{box-shadow:none !important}.collapse{display:none}.collapse.show{display:block}.fade{opacity:0;transition:opacity 0.15s linear}.fade.show{opacity:1}.clearfix::after{display:block;clear:both;content:""}.cursor-pointer{cursor:pointer !important}.btn-close{box-sizing:content-box;width:1em;height:1em;padding:0.25em;color:#000;background:transparent;border:0;opacity:0.5;cursor:pointer;font-size:1.25rem;line-height:1}.btn-close:hover{opacity:0.75}.btn-close:focus{opacity:1;outline:0;box-shadow:0 0 0 0.2rem rgba(74,144,217,0.25)}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:0.25rem;background-color:#fff;border:1px solid rgba(0,0,0,0.1);border-radius:.25rem;max-width:100%;height:auto}.border-top{border-top:1px solid rgba(0,0,0,0.1) !important}.border-bottom{border-bottom:1px solid rgba(0,0,0,0.1) !important}.align-middle{vertical-align:middle !important}.align-top{vertical-align:top !important}.align-bottom{vertical-align:bottom !important}.float-start{float:left !important}.float-end{float:right !important}.float-none{float:none !important}.visually-hidden{position:absolute !important;width:1px !important;height:1px !important;padding:0 !important;margin:-1px !important;overflow:hidden !important;clip:rect(0, 0, 0, 0) !important;white-space:nowrap !important;border:0 !important}
1
+ *,*::before,*::after{box-sizing:border-box}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;scroll-behavior:smooth}body{margin:0;font-family:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.25}p{margin-top:0;margin-bottom:1rem}ol,ul{padding-left:2rem;margin-top:0;margin-bottom:1rem}a{color:#4a90d9;text-decoration:none}a:hover{color:#256ab1;text-decoration:underline}img,svg{max-width:100%;height:auto;vertical-align:middle}table{border-collapse:collapse;caption-side:bottom}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;cursor:pointer}hr{margin:1rem 0;color:inherit;border:0;border-top:1px solid rgba(0,0,0,.1);opacity:.25}pre,code,kbd,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}[hidden]{display:none !important}h1{font-size:2.25rem}h2{font-size:1.875rem}h3{font-size:1.5rem}h4{font-size:1.25rem}h5{font-size:1.125rem}h6{font-size:1rem}small,.small{font-size:.875em}mark,.mark{padding:.2em;background-color:rgba(255,193,7,.3)}blockquote{margin:0 0 1rem;padding:.5rem 1rem;border-left:.25rem solid rgba(0,0,0,.1);font-size:1.125rem}code{font-size:.875em;color:#dc3545;word-wrap:break-word}pre{display:block;padding:1rem;font-size:.875em;color:#212529;background-color:#f8f9fa;border-radius:.25rem}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.container,.container-fluid{width:100%;padding-right:.75rem;padding-left:.75rem;margin-right:auto;margin-left:auto}@media(min-width: 576px){.container{max-width:540px}}@media(min-width: 768px){.container{max-width:720px}}@media(min-width: 992px){.container{max-width:960px}}@media(min-width: 1200px){.container{max-width:1140px}}@media(min-width: 1400px){.container{max-width:1320px}}.row{display:flex;flex-wrap:wrap;margin-right:-0.75rem;margin-left:-0.75rem}.row>*{flex-shrink:0;width:100%;max-width:100%;padding-right:.75rem;padding-left:.75rem}.col{flex:1 0 0%}.col-1{flex:0 0 auto;width:8.3333333333%}.col-2{flex:0 0 auto;width:16.6666666667%}.col-3{flex:0 0 auto;width:25%}.col-4{flex:0 0 auto;width:33.3333333333%}.col-5{flex:0 0 auto;width:41.6666666667%}.col-6{flex:0 0 auto;width:50%}.col-7{flex:0 0 auto;width:58.3333333333%}.col-8{flex:0 0 auto;width:66.6666666667%}.col-9{flex:0 0 auto;width:75%}.col-10{flex:0 0 auto;width:83.3333333333%}.col-11{flex:0 0 auto;width:91.6666666667%}.col-12{flex:0 0 auto;width:100%}.offset-1{margin-left:8.3333333333%}.offset-2{margin-left:16.6666666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.3333333333%}.offset-0{margin-left:0}@media(min-width: 576px){.col-sm{flex:1 0 0%}.col-sm-1{flex:0 0 auto;width:8.3333333333%}.col-sm-2{flex:0 0 auto;width:16.6666666667%}.col-sm-3{flex:0 0 auto;width:25%}.col-sm-4{flex:0 0 auto;width:33.3333333333%}.col-sm-5{flex:0 0 auto;width:41.6666666667%}.col-sm-6{flex:0 0 auto;width:50%}.col-sm-7{flex:0 0 auto;width:58.3333333333%}.col-sm-8{flex:0 0 auto;width:66.6666666667%}.col-sm-9{flex:0 0 auto;width:75%}.col-sm-10{flex:0 0 auto;width:83.3333333333%}.col-sm-11{flex:0 0 auto;width:91.6666666667%}.col-sm-12{flex:0 0 auto;width:100%}.offset-sm-0{margin-left:0}}@media(min-width: 768px){.col-md{flex:1 0 0%}.col-md-1{flex:0 0 auto;width:8.3333333333%}.col-md-2{flex:0 0 auto;width:16.6666666667%}.col-md-3{flex:0 0 auto;width:25%}.col-md-4{flex:0 0 auto;width:33.3333333333%}.col-md-5{flex:0 0 auto;width:41.6666666667%}.col-md-6{flex:0 0 auto;width:50%}.col-md-7{flex:0 0 auto;width:58.3333333333%}.col-md-8{flex:0 0 auto;width:66.6666666667%}.col-md-9{flex:0 0 auto;width:75%}.col-md-10{flex:0 0 auto;width:83.3333333333%}.col-md-11{flex:0 0 auto;width:91.6666666667%}.col-md-12{flex:0 0 auto;width:100%}.offset-md-0{margin-left:0}}@media(min-width: 992px){.col-lg{flex:1 0 0%}.col-lg-1{flex:0 0 auto;width:8.3333333333%}.col-lg-2{flex:0 0 auto;width:16.6666666667%}.col-lg-3{flex:0 0 auto;width:25%}.col-lg-4{flex:0 0 auto;width:33.3333333333%}.col-lg-5{flex:0 0 auto;width:41.6666666667%}.col-lg-6{flex:0 0 auto;width:50%}.col-lg-7{flex:0 0 auto;width:58.3333333333%}.col-lg-8{flex:0 0 auto;width:66.6666666667%}.col-lg-9{flex:0 0 auto;width:75%}.col-lg-10{flex:0 0 auto;width:83.3333333333%}.col-lg-11{flex:0 0 auto;width:91.6666666667%}.col-lg-12{flex:0 0 auto;width:100%}.offset-lg-0{margin-left:0}}@media(min-width: 1200px){.col-xl{flex:1 0 0%}.col-xl-1{flex:0 0 auto;width:8.3333333333%}.col-xl-2{flex:0 0 auto;width:16.6666666667%}.col-xl-3{flex:0 0 auto;width:25%}.col-xl-4{flex:0 0 auto;width:33.3333333333%}.col-xl-5{flex:0 0 auto;width:41.6666666667%}.col-xl-6{flex:0 0 auto;width:50%}.col-xl-7{flex:0 0 auto;width:58.3333333333%}.col-xl-8{flex:0 0 auto;width:66.6666666667%}.col-xl-9{flex:0 0 auto;width:75%}.col-xl-10{flex:0 0 auto;width:83.3333333333%}.col-xl-11{flex:0 0 auto;width:91.6666666667%}.col-xl-12{flex:0 0 auto;width:100%}.offset-xl-0{margin-left:0}}.justify-start{justify-content:flex-start}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-between{justify-content:space-between}.justify-around{justify-content:space-around}.align-start{align-items:flex-start}.align-center{align-items:center}.align-end{align-items:flex-end}.btn{display:inline-block;font-weight:400;line-height:1.5;color:#212529;text-align:center;vertical-align:middle;cursor:pointer;user-select:none;background-color:rgba(0,0,0,0);border:1px solid rgba(0,0,0,0);padding:.375rem .75rem;font-size:1rem;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;text-decoration:none}.btn:disabled,.btn.disabled{pointer-events:none;opacity:.65}.btn-primary{color:#fff;background-color:#4a90d9;border-color:#4a90d9}.btn-primary:hover{background-color:#2b7bcf;border-color:#2a76c6}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{background-color:#596167;border-color:#545b62}.btn-success{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:hover{background-color:#208637;border-color:#1e7e34}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{background-color:#c62232;border-color:#bd2130}.btn-warning{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{background-color:#dda600;border-color:#d39e00}.btn-info{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:hover{background-color:#128294;border-color:#117a8b}.btn-light{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{background-color:#e0e5e9;border-color:#dae0e5}.btn-dark{color:#fff;background-color:#212529;border-color:#212529}.btn-dark:hover{background-color:#0f1112;border-color:#0a0c0d}.btn-outline-primary{color:#4a90d9;border-color:#4a90d9;background-color:rgba(0,0,0,0)}.btn-outline-primary:hover{color:#fff;background-color:#4a90d9}.btn-outline-secondary{color:#6c757d;border-color:#6c757d;background-color:rgba(0,0,0,0)}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d}.btn-outline-success{color:#28a745;border-color:#28a745;background-color:rgba(0,0,0,0)}.btn-outline-success:hover{color:#fff;background-color:#28a745}.btn-outline-danger{color:#dc3545;border-color:#dc3545;background-color:rgba(0,0,0,0)}.btn-outline-danger:hover{color:#fff;background-color:#dc3545}.btn-outline-warning{color:#ffc107;border-color:#ffc107;background-color:rgba(0,0,0,0)}.btn-outline-warning:hover{color:#212529;background-color:#ffc107}.btn-outline-info{color:#17a2b8;border-color:#17a2b8;background-color:rgba(0,0,0,0)}.btn-outline-info:hover{color:#fff;background-color:#17a2b8}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa;background-color:rgba(0,0,0,0)}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa}.btn-outline-dark{color:#212529;border-color:#212529;background-color:rgba(0,0,0,0)}.btn-outline-dark:hover{color:#fff;background-color:#212529}.btn-sm{padding:.25rem .5rem;font-size:.875rem;border-radius:.25rem}.btn-lg{padding:.5rem 1rem;font-size:1.125rem;border-radius:.5rem}.btn-block{display:block;width:100%}.form-group{margin-bottom:1rem}.form-label{display:inline-block;margin-bottom:.5rem;font-weight:700}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}.form-control:focus{color:#212529;background-color:#fff;border-color:#b3d1ef;outline:0;box-shadow:0 0 0 .2rem rgba(74,144,217,.25)}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled{background-color:#f8f9fa;opacity:1}textarea.form-control{min-height:calc(1.5em + .75rem + 2px)}.form-select{display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}.form-select:focus{border-color:#b3d1ef;outline:0;box-shadow:0 0 0 .2rem rgba(74,144,217,.25)}.form-check{display:block;min-height:1.5rem;padding-left:1.5em;margin-bottom:.125rem}.form-check-input{float:left;width:1em;height:1em;margin-left:-1.5em;margin-top:.25em;appearance:none;background-color:#fff;border:1px solid rgba(0,0,0,.25)}.form-check-input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio]{border-radius:50%}.form-check-input:checked{background-color:#4a90d9;border-color:#4a90d9}.form-check-input:focus{border-color:#b3d1ef;outline:0;box-shadow:0 0 0 .2rem rgba(74,144,217,.25)}.form-check-label{cursor:pointer}.is-valid{border-color:#28a745 !important}.is-valid:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.25) !important}.is-invalid{border-color:#dc3545 !important}.is-invalid:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.25) !important}.valid-feedback,.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em}.valid-feedback{color:#28a745}.invalid-feedback{color:#dc3545}.is-valid~.valid-feedback,.is-invalid~.invalid-feedback{display:block}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-select{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;font-size:1rem;line-height:1.5;color:#212529;text-align:center;white-space:nowrap;background-color:#f8f9fa;border:1px solid #ced4da;border-radius:.25rem}.card{position:relative;display:flex;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.5rem}.card-header{padding:.75rem 1rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.5rem - 1px) calc(.5rem - 1px) 0 0}.card-body{flex:1 1 auto;padding:1rem}.card-footer{padding:.75rem 1rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.5rem - 1px) calc(.5rem - 1px)}.card-title{margin-bottom:.5rem;font-weight:700}.card-text:last-child{margin-bottom:0}.card-img-top{width:100%;border-top-left-radius:calc(.5rem - 1px);border-top-right-radius:calc(.5rem - 1px)}.nav{display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem;color:#4a90d9;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out}.nav-link:hover,.nav-link:focus{color:#256ab1}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-link.active{color:#4a90d9;font-weight:700}.navbar{position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding:.5rem 1rem}.navbar-brand{padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;font-weight:700;color:inherit;text-decoration:none;white-space:nowrap}.navbar-brand:hover{color:inherit;opacity:.8}.navbar-nav{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-toggler{padding:.25rem .75rem;font-size:1.125rem;line-height:1;background-color:rgba(0,0,0,0);border:1px solid rgba(0,0,0,.1);border-radius:.25rem;cursor:pointer}.navbar-toggler:focus{outline:0;box-shadow:0 0 0 .2rem rgba(74,144,217,.25)}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center;display:none}.navbar-collapse.show{display:flex}@media(min-width: 576px){.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-collapse{display:flex;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media(min-width: 768px){.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-collapse{display:flex;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media(min-width: 992px){.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-collapse{display:flex;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media(min-width: 1200px){.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-collapse{display:flex;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}@media(min-width: 1400px){.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-collapse{display:flex;flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}}@media(min-width: 992px){.navbar-nav{flex-direction:row}.navbar-collapse{display:flex;flex-basis:auto}.navbar-toggler{display:none}}.navbar-dark{color:#fff;background-color:#212529}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .nav-link{color:rgba(255,255,255,.75)}.navbar-dark .nav-link:hover,.navbar-dark .nav-link.active{color:#fff}.navbar-light{background-color:#f8f9fa}.navbar-light .nav-link{color:rgba(0,0,0,.55)}.navbar-light .nav-link:hover,.navbar-light .nav-link.active{color:rgba(0,0,0,.9)}.breadcrumb{display:flex;flex-wrap:wrap;padding:.5rem 1rem;margin-bottom:1rem;list-style:none;background-color:#f8f9fa;border-radius:.25rem}.breadcrumb-item+.breadcrumb-item::before{display:inline-block;padding-right:.5rem;padding-left:.5rem;color:#6c757d;content:"/"}.breadcrumb-item.active{color:#6c757d}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal.show{display:block}.modal-dialog{position:relative;width:auto;margin:1.75rem auto;max-width:500px}.modal-sm{max-width:300px}.modal-lg{max-width:800px}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.5rem;box-shadow:0 .5rem 1rem rgba(0,0,0,.15);outline:0}.modal-header{display:flex;flex-shrink:0;align-items:center;justify-content:space-between;padding:1rem;border-bottom:1px solid rgba(0,0,0,.1)}.modal-title{margin-bottom:0;font-weight:700}.modal-body{position:relative;flex:1 1 auto;padding:1rem}.modal-footer{display:flex;flex-shrink:0;flex-wrap:wrap;align-items:center;justify-content:flex-end;padding:.75rem;border-top:1px solid rgba(0,0,0,.1);gap:.5rem}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:rgba(0,0,0,.5);display:none}.modal-backdrop.show{display:block}.alert{position:relative;padding:.75rem 1rem;margin-bottom:1rem;border:1px solid rgba(0,0,0,0);border-radius:.25rem}.alert-primary{color:#1c5187;background-color:#deeaf8;border-color:#b3d1ef}.alert-secondary{color:#313539;background-color:#caced1;border-color:#afb5ba}.alert-success{color:#0f401b;background-color:#9be7ac;border-color:#71dd8a}.alert-danger{color:#7c151f;background-color:#f6cdd1;border-color:#efa2a9}.alert-warning{color:#876500;background-color:#ffeeba;border-color:#ffe187}.alert-info{color:#093e47;background-color:#90e4f1;border-color:#63d9ec}.alert-light{color:#aeb9c5;background-color:#fff;border-color:#fff}.alert-dark{color:#000;background-color:#717e8c;border-color:#5a6570}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;padding:.9375rem 1rem;background:rgba(0,0,0,0);border:0;cursor:pointer;color:inherit;opacity:.5}.alert-dismissible .btn-close:hover{opacity:.75}.table{width:100%;margin-bottom:1rem;vertical-align:top;border-color:rgba(0,0,0,.1)}.table>:not(caption)>*>*{padding:.5rem;border-bottom-width:1px;border-bottom-style:solid;border-bottom-color:inherit}.table>thead{vertical-align:bottom;border-bottom:2px solid currentColor}.table>tbody>tr:last-child>*{border-bottom-color:rgba(0,0,0,0)}.table-sm>:not(caption)>*>*{padding:.25rem}.table-bordered{border:1px solid rgba(0,0,0,.1)}.table-bordered>:not(caption)>*>*{border-width:1px;border-style:solid;border-color:inherit}.table-striped>tbody>tr:nth-of-type(odd)>*{background-color:rgba(0,0,0,.02)}.table-hover>tbody>tr:hover>*{background-color:rgba(0,0,0,.04)}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}.badge{display:inline-block;padding:.25em .65em;font-size:.75em;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:50rem}.badge-primary{color:#fff;background-color:#4a90d9}.badge-secondary{color:#fff;background-color:#6c757d}.badge-success{color:#fff;background-color:#28a745}.badge-danger{color:#fff;background-color:#dc3545}.badge-warning{color:#212529;background-color:#ffc107}.badge-info{color:#fff;background-color:#17a2b8}.badge-light{color:#212529;background-color:#f8f9fa}.badge-dark{color:#fff;background-color:#212529}.pagination{display:flex;flex-wrap:wrap;padding-left:0;list-style:none;gap:.25rem}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;background-color:#fff;border-color:rgba(0,0,0,.1)}.page-item.active .page-link{color:#fff;background-color:#4a90d9;border-color:#4a90d9}.page-link{display:block;padding:.375rem .75rem;color:#4a90d9;background-color:#fff;border:1px solid rgba(0,0,0,.15);border-radius:.25rem;text-decoration:none;line-height:1.25;transition:color .15s,background-color .15s,border-color .15s}.page-link:hover{color:#2a76c6;background-color:rgba(74,144,217,.08);border-color:rgba(0,0,0,.2)}.page-link:focus{outline:0;box-shadow:0 0 0 .2rem rgba(74,144,217,.25)}.crud-img-preview{max-height:200px;max-width:100%;border-radius:.25rem;object-fit:cover}.crud-img-preview-sm{max-height:180px;max-width:100%;border-radius:.25rem;object-fit:cover}.d-none{display:none !important}.d-block{display:block !important}.d-flex{display:flex !important}.d-inline{display:inline !important}.d-inline-block{display:inline-block !important}.d-grid{display:grid !important}@media(min-width: 576px){.d-sm-none{display:none !important}.d-sm-block{display:block !important}.d-sm-flex{display:flex !important}.d-sm-inline{display:inline !important}.d-sm-inline-block{display:inline-block !important}}@media(min-width: 768px){.d-md-none{display:none !important}.d-md-block{display:block !important}.d-md-flex{display:flex !important}.d-md-inline{display:inline !important}.d-md-inline-block{display:inline-block !important}}@media(min-width: 992px){.d-lg-none{display:none !important}.d-lg-block{display:block !important}.d-lg-flex{display:flex !important}.d-lg-inline{display:inline !important}.d-lg-inline-block{display:inline-block !important}}.flex-row{flex-direction:row !important}.flex-column{flex-direction:column !important}.flex-wrap{flex-wrap:wrap !important}.flex-nowrap{flex-wrap:nowrap !important}.flex-grow-0{flex-grow:0 !important}.flex-grow-1{flex-grow:1 !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}.align-items-start{align-items:flex-start !important}.align-items-end{align-items:flex-end !important}.align-items-center{align-items:center !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}.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}.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}.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}.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}.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}.my-0{margin-top:0 !important;margin-bottom:0 !important}.mx-0{margin-left:0 !important;margin-right:0 !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.mx-1{margin-left:.25rem !important;margin-right:.25rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.mx-2{margin-left:.5rem !important;margin-right:.5rem !important}.my-3{margin-top:1rem !important;margin-bottom:1rem !important}.mx-3{margin-left:1rem !important;margin-right:1rem !important}.my-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.mx-4{margin-left:1.5rem !important;margin-right:1.5rem !important}.my-5{margin-top:3rem !important;margin-bottom:3rem !important}.mx-5{margin-left:3rem !important;margin-right:3rem !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}.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}.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}.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}.py-0{padding-top:0 !important;padding-bottom:0 !important}.px-0{padding-left:0 !important;padding-right:0 !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.px-1{padding-left:.25rem !important;padding-right:.25rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.px-2{padding-left:.5rem !important;padding-right:.5rem !important}.py-3{padding-top:1rem !important;padding-bottom:1rem !important}.px-3{padding-left:1rem !important;padding-right:1rem !important}.py-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.px-4{padding-left:1.5rem !important;padding-right:1.5rem !important}.py-5{padding-top:3rem !important;padding-bottom:3rem !important}.px-5{padding-left:3rem !important;padding-right:3rem !important}.mx-auto{margin-left:auto !important;margin-right:auto !important}.text-start{text-align:left !important}.text-center{text-align:center !important}.text-end{text-align:right !important}.text-uppercase{text-transform:uppercase !important}.text-capitalize{text-transform:capitalize !important}.text-nowrap{white-space:nowrap !important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fw-bold{font-weight:700 !important}.fw-normal{font-weight:400 !important}.fw-light{font-weight:300 !important}.fs-1{font-size:2.25rem !important}.fs-2{font-size:1.875rem !important}.fs-3{font-size:1.5rem !important}.fs-4{font-size:1.25rem !important}.fs-5{font-size:1rem !important}.fs-6{font-size:.875rem !important}.text-primary{color:#4a90d9 !important}.text-secondary{color:#6c757d !important}.text-success{color:#28a745 !important}.text-danger{color:#dc3545 !important}.text-warning{color:#ffc107 !important}.text-info{color:#17a2b8 !important}.text-light{color:#f8f9fa !important}.text-dark{color:#212529 !important}.text-muted{color:#6c757d !important}.text-white{color:#fff !important}.bg-primary{background-color:#4a90d9 !important}.bg-secondary{background-color:#6c757d !important}.bg-success{background-color:#28a745 !important}.bg-danger{background-color:#dc3545 !important}.bg-warning{background-color:#ffc107 !important}.bg-info{background-color:#17a2b8 !important}.bg-light{background-color:#f8f9fa !important}.bg-dark{background-color:#212529 !important}.bg-white{background-color:#fff !important}.border{border:1px solid rgba(0,0,0,.1) !important}.border-0{border:0 !important}.rounded{border-radius:.25rem !important}.rounded-0{border-radius:0 !important}.rounded-circle{border-radius:50% !important}.rounded-pill{border-radius:50rem !important}.w-25{width:25% !important}.h-25{height:25% !important}.w-50{width:50% !important}.h-50{height:50% !important}.w-75{width:75% !important}.h-75{height:75% !important}.w-100{width:100% !important}.h-100{height:100% !important}.mw-100{max-width:100% !important}.mh-100{max-height:100% !important}.position-relative{position:relative !important}.position-absolute{position:absolute !important}.position-fixed{position:fixed !important}.position-sticky{position:sticky !important}.position-static{position:static !important}.visible{visibility:visible !important}.invisible{visibility:hidden !important}.overflow-hidden{overflow:hidden !important}.overflow-auto{overflow:auto !important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15) !important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075) !important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175) !important}.shadow-none{box-shadow:none !important}.collapse{display:none}.collapse.show{display:block}.fade{opacity:0;transition:opacity .15s linear}.fade.show{opacity:1}.clearfix::after{display:block;clear:both;content:""}.cursor-pointer{cursor:pointer !important}.btn-close{box-sizing:content-box;width:1em;height:1em;padding:.25em;color:#000;background:rgba(0,0,0,0);border:0;opacity:.5;cursor:pointer;font-size:1.25rem;line-height:1}.btn-close:hover{opacity:.75}.btn-close:focus{opacity:1;outline:0;box-shadow:0 0 0 .2rem rgba(74,144,217,.25)}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid rgba(0,0,0,.1);border-radius:.25rem;max-width:100%;height:auto}.border-top{border-top:1px solid rgba(0,0,0,.1) !important}.border-bottom{border-bottom:1px solid rgba(0,0,0,.1) !important}.align-middle{vertical-align:middle !important}.align-top{vertical-align:top !important}.align-bottom{vertical-align:bottom !important}.float-start{float:left !important}.float-end{float:right !important}.float-none{float:none !important}.visually-hidden{position:absolute !important;width:1px !important;height:1px !important;padding:0 !important;margin:-1px !important;overflow:hidden !important;clip:rect(0, 0, 0, 0) !important;white-space:nowrap !important;border:0 !important}
@@ -19,6 +19,16 @@ module Tina4
19
19
  end
20
20
 
21
21
  class RackApp
22
+ class << self
23
+ # The process-wide RackApp — the app actually serving traffic. Set by
24
+ # #initialize (last one wins, the same convention as
25
+ # Tina4::WebSocket.current). Tina4::TestClient defaults to this so an
26
+ # in-process test request runs through the SAME app object a live request
27
+ # does, instead of building a second one with a different static root and
28
+ # clobbering the shared WebSocket engine. (feature-recount D6)
29
+ attr_accessor :current
30
+ end
31
+
22
32
  STATIC_DIRS = %w[public src/public src/assets assets].freeze
23
33
 
24
34
  # CORS is now handled by Tina4::CorsMiddleware
@@ -46,6 +56,9 @@ module Tina4
46
56
  # manager. Without this the handshake never matches a route and falls
47
57
  # through to 404, silently degrading the whole reloader to polling.
48
58
  RackApp.register_dev_reload_ws if dev_mode?
59
+
60
+ # Publish as the process-wide app (see RackApp.current).
61
+ RackApp.current = self
49
62
  end
50
63
 
51
64
  # WebSocket handler for the dev-reload channel (/__dev_reload).
data/lib/tina4/router.rb CHANGED
@@ -615,8 +615,19 @@ module Tina4
615
615
  @last_routes_dir = nil
616
616
  end
617
617
 
618
- # Write a .broken sentinel so /health and the dev dashboard surface
619
- # auto-discover failures instead of swallowing them into a log line.
618
+ # Write a .broken sentinel to data/.broken/ so an auto-discover failure
619
+ # leaves a durable on-disk artifact instead of being swallowed into a log
620
+ # line.
621
+ #
622
+ # Ruby only WRITES these files today — nothing under lib/ reads them back.
623
+ # lib/tina4/health.rb contains no `broken` reference, and
624
+ # GET /__dev/api/broken serves the in-memory Tina4::DevAdmin::ErrorTracker
625
+ # (its own JSON store under Dir.tmpdir, dev_admin.rb:127-132), not this
626
+ # directory. Python's /health DOES glob data/.broken and answer 503 with
627
+ # `errors` + `latest_error` (tina4-python/tina4_python/core/server.py:296-320);
628
+ # mirroring that read side in Ruby is an OPEN parity gap. This comment used
629
+ # to claim "/health and the dev dashboard surface" it, which was false in
630
+ # both halves. (feature-recount D12)
620
631
  def record_broken_route_import(file, error)
621
632
  broken_dir = File.join(Dir.pwd, "data", ".broken")
622
633
  FileUtils.mkdir_p(broken_dir) unless Dir.exist?(broken_dir)
@@ -0,0 +1,63 @@
1
+ // Tina4 CSS Framework - Pagination
2
+ // ----------------------------------
3
+
4
+ .pagination {
5
+ display: flex;
6
+ flex-wrap: wrap;
7
+ padding-left: 0;
8
+ list-style: none;
9
+ gap: 0.25rem;
10
+ }
11
+
12
+ .page-item {
13
+ &.disabled .page-link {
14
+ color: $muted;
15
+ pointer-events: none;
16
+ background-color: $body-bg;
17
+ border-color: rgba($black, 0.1);
18
+ }
19
+
20
+ &.active .page-link {
21
+ color: $white;
22
+ background-color: $primary;
23
+ border-color: $primary;
24
+ }
25
+ }
26
+
27
+ .page-link {
28
+ display: block;
29
+ padding: 0.375rem 0.75rem;
30
+ color: $primary;
31
+ background-color: $body-bg;
32
+ border: 1px solid rgba($black, 0.15);
33
+ border-radius: $border-radius;
34
+ text-decoration: none;
35
+ line-height: 1.25;
36
+ transition: color 0.15s, background-color 0.15s, border-color 0.15s;
37
+
38
+ &:hover {
39
+ color: darken($primary, 10%);
40
+ background-color: rgba($primary, 0.08);
41
+ border-color: rgba($black, 0.2);
42
+ }
43
+
44
+ &:focus {
45
+ outline: 0;
46
+ box-shadow: 0 0 0 0.2rem rgba($primary, 0.25);
47
+ }
48
+ }
49
+
50
+ // CRUD component image preview
51
+ .crud-img-preview {
52
+ max-height: 200px;
53
+ max-width: 100%;
54
+ border-radius: $border-radius;
55
+ object-fit: cover;
56
+ }
57
+
58
+ .crud-img-preview-sm {
59
+ max-height: 180px;
60
+ max-width: 100%;
61
+ border-radius: $border-radius;
62
+ object-fit: cover;
63
+ }
@@ -61,6 +61,17 @@ $spacing-dirs: (
61
61
  .#{$abbr}#{$dir}-#{$key} { #{$prop}#{$suffix}: $val !important; }
62
62
  }
63
63
  }
64
+ // Axis shortcuts: py-* (top+bottom), px-* (left+right)
65
+ @each $key, $val in $spacers {
66
+ .#{$abbr}y-#{$key} {
67
+ #{$prop}-top: $val !important;
68
+ #{$prop}-bottom: $val !important;
69
+ }
70
+ .#{$abbr}x-#{$key} {
71
+ #{$prop}-left: $val !important;
72
+ #{$prop}-right: $val !important;
73
+ }
74
+ }
64
75
  }
65
76
 
66
77
  .mx-auto { margin-left: auto !important; margin-right: auto !important; }
@@ -14,4 +14,5 @@
14
14
  @import 'alerts';
15
15
  @import 'tables';
16
16
  @import 'badges';
17
+ @import 'pagination';
17
18
  @import 'utilities';
@@ -6,6 +6,15 @@ module Tina4
6
6
  SCSS_DIRS = %w[src/scss scss src/styles styles].freeze
7
7
  CSS_OUTPUT = "src/public/css"
8
8
 
9
+ # Flags that may trail a variable declaration's value. `!default` means
10
+ # "assign only if this variable is not already set" -- the flag that makes a
11
+ # variable themeable. `!global` is the scope flag. Both are compiler
12
+ # directives: they are consumed at the declaration and must never reach the
13
+ # CSS, because `padding: 1.5rem !default` is invalid CSS and browsers drop
14
+ # the whole declaration. Sass flag names are case-SENSITIVE (`!DEFAULT` is an
15
+ # error in Dart Sass), so the match is deliberately case-sensitive.
16
+ VARIABLE_FLAG = /\s*!(default|global)\s*\z/
17
+
9
18
  # Module-level state for import paths and variables
10
19
  @import_paths = []
11
20
  @variables = {}
@@ -92,14 +101,50 @@ module Tina4
92
101
 
93
102
  private
94
103
 
104
+ # Split trailing !default / !global flags off a variable declaration value.
105
+ # Returns [value_without_flags, declares_default].
106
+ #
107
+ # Only ever called on the value of a `$name: value;` declaration, so a
108
+ # literal !default anywhere else -- inside a quoted string
109
+ # (`content: "x !default y"`) or a function argument -- is left untouched,
110
+ # exactly as Dart Sass leaves it. A blanket strip would corrupt real string
111
+ # content, and would silently turn `rgba(#000 !default, 0.1)` (a syntax
112
+ # error in Dart Sass) into valid-looking CSS that Sass would never emit.
113
+ #
114
+ # NOTE: this runs a regex, so it clobbers the caller's match globals --
115
+ # callers must capture their own groups first.
116
+ def strip_variable_flags(value)
117
+ declares_default = false
118
+ loop do
119
+ match = VARIABLE_FLAG.match(value)
120
+ break unless match
121
+
122
+ declares_default = true if match[1] == "default"
123
+ value = value[0, match.begin(0)]
124
+ end
125
+ [value.strip, declares_default]
126
+ end
127
+
95
128
  def basic_compile(content, base_dir)
96
129
  # Handle @import
97
130
  content = process_imports(content, base_dir)
98
131
 
99
- # Handle variables
132
+ # Handle variables. The /m flag makes the value span newlines, matching
133
+ # the Python master's `[^;]+`: without it a MULTI-LINE declaration (a map
134
+ # literal with its closing `) !default;` on its own line -- exactly what
135
+ # tina4-css's _variables.scss uses) was never extracted, and the raw SCSS
136
+ # was dumped verbatim into the CSS.
100
137
  variables = {}
101
- content = content.gsub(/\$([a-zA-Z_][\w-]*)\s*:\s*(.+?);/) do
102
- variables[Regexp.last_match(1)] = Regexp.last_match(2).strip
138
+ content = content.gsub(/\$([a-zA-Z_][\w-]*)\s*:\s*(.+?);/m) do
139
+ # Capture BOTH groups before calling strip_variable_flags -- it runs a
140
+ # regex, which clobbers the match globals this block relies on.
141
+ name = Regexp.last_match(1)
142
+ raw = Regexp.last_match(2).strip
143
+ value, declares_default = strip_variable_flags(raw)
144
+ # !default must not overwrite a value that is already set.
145
+ next "" if declares_default && variables.fetch(name, "null") != "null"
146
+
147
+ variables[name] = value
103
148
  ""
104
149
  end
105
150
 
@@ -47,6 +47,23 @@ module Tina4
47
47
  end
48
48
 
49
49
  class TestClient
50
+ # +app+ — the Rack app every request is dispatched through. Defaults to the
51
+ # process-wide Tina4::RackApp (RackApp.current): inside a running server that
52
+ # IS the app serving traffic, so an in-process test request and a live request
53
+ # go through the same object. With no app yet (a bare spec process) one is
54
+ # built lazily, rooted at +root_dir+ / Tina4.root_dir / Dir.pwd.
55
+ #
56
+ # Mirrors Node's `new TestClient(router?)` optional-collaborator constructor.
57
+ def initialize(app = nil, root_dir: nil)
58
+ @app = app
59
+ @root_dir = root_dir
60
+ end
61
+
62
+ def app
63
+ @app ||= Tina4::RackApp.current ||
64
+ Tina4::RackApp.new(root_dir: @root_dir || Tina4.root_dir || Dir.pwd)
65
+ end
66
+
50
67
  # Send a GET request.
51
68
  def get(path, headers: nil)
52
69
  request("GET", path, headers: headers)
@@ -74,7 +91,19 @@ module Tina4
74
91
 
75
92
  private
76
93
 
77
- # Build a mock Rack env, match the route, execute the handler.
94
+ # Build a Rack env and dispatch it through the REAL Tina4::RackApp the same
95
+ # front controller a live HTTP request goes through.
96
+ #
97
+ # This used to call Tina4::Router.match directly and invoke the handler
98
+ # itself, which made everything RackApp does around route matching invisible
99
+ # to tests: global middleware never ran, and static files, /swagger,
100
+ # /swagger/openapi.json, the bundled framework assets, /__dev, /__feedback,
101
+ # CORS preflight, the RFC 9110 OPTIONS/405 Allow responses and HEAD
102
+ # body-stripping all came back as a hand-built 404 {"error":"Not found"} while
103
+ # the live server served them 200. Any spec asserting framework endpoint
104
+ # behaviour through TestClient was asserting nothing. (feature-recount D6 —
105
+ # the same shape as the #PY2 auth fix: the in-process test client must route
106
+ # through the real pipeline, not a re-implementation of it.)
78
107
  def request(method, path, json: nil, body: nil, headers: nil)
79
108
  # Build raw body
80
109
  raw_body = ""
@@ -99,7 +128,9 @@ module Tina4
99
128
  "SERVER_PORT" => "7145",
100
129
  "HTTP_HOST" => "localhost:7145",
101
130
  "REMOTE_ADDR" => "127.0.0.1",
131
+ "SERVER_PROTOCOL" => "HTTP/1.1",
102
132
  "rack.input" => StringIO.new(raw_body),
133
+ "rack.errors" => $stderr,
103
134
  "rack.url_scheme" => "http"
104
135
  }
105
136
 
@@ -115,57 +146,13 @@ module Tina4
115
146
  end
116
147
  end
117
148
 
118
- # Match route
119
- result = Tina4::Router.match(method.upcase, clean_path)
120
-
121
- unless result
122
- return TestResponse.new([404, { "content-type" => "application/json" }, ['{"error":"Not found"}']])
123
- end
124
-
125
- route, path_params = result
126
-
127
- # Route through the REAL auth gate (parity with the live server). A write
128
- # to an auth-required route (POST/PUT/PATCH/DELETE by default, or any
129
- # secured route) with no valid token / API key / formToken 401s here
130
- # exactly as it would in production. The TestClient used to skip this and
131
- # run the handler directly, so a green test could hide a live 401 — the
132
- # verification layer lied. A public route (GET, or a write marked
133
- # .no_auth) has auth_required false, so this is a no-op. (#PY2 parity)
134
- unauthorized = Tina4::RackApp.enforce_route_auth(env, route)
135
- return TestResponse.new(unauthorized) if unauthorized
136
-
137
- # Create request and response
138
- req = Tina4::Request.new(env, path_params || {})
139
- res = Tina4::Response.new
140
-
141
- # Build handler args (same logic as RackApp.handle_route)
142
- handler_params = route.handler.parameters.map(&:last)
143
- route_params = path_params || {}
144
- args = handler_params.map do |name|
145
- if route_params.key?(name)
146
- route_params[name]
147
- elsif name == :request || name == :req
148
- req
149
- else
150
- res
151
- end
152
- end
153
-
154
- # Execute handler
155
- handler_result = args.empty? ? route.handler.call : route.handler.call(*args)
156
-
157
- # Auto-detect response type
158
- if handler_result.is_a?(Tina4::Response)
159
- final = handler_result
160
- elsif route.respond_to?(:template) && route.template && handler_result.is_a?(Hash)
161
- html = Tina4::Template.render(route.template, handler_result)
162
- res.html(html)
163
- final = res
164
- else
165
- final = Tina4::Response.auto_detect(handler_result, res)
166
- end
167
-
168
- TestResponse.new(final.to_rack)
149
+ # Dispatch through the real front controller. Everything the live server
150
+ # does — the secure-by-default auth gate (#PY2), global + per-route
151
+ # middleware, route matching and handler invocation, template rendering,
152
+ # response auto-detection, static files, /swagger, /__dev, /__feedback, the
153
+ # RFC 9110 OPTIONS/405 responses, HEAD body-stripping, session save and the
154
+ # 500 handler — is now exercised exactly once, in one place.
155
+ TestResponse.new(app.call(env))
169
156
  end
170
157
  end
171
158
  end
data/lib/tina4/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tina4
4
- VERSION = "3.13.81"
4
+ VERSION = "3.13.82"
5
5
  end
data/lib/tina4.rb CHANGED
@@ -42,7 +42,7 @@ require_relative "tina4/messenger"
42
42
  require_relative "tina4/dev_mailbox"
43
43
  require_relative "tina4/ai"
44
44
  require_relative "tina4/cache"
45
- require_relative "tina4/sql_translation"
45
+ require_relative "tina4/sql_translator"
46
46
  require_relative "tina4/cache_backends"
47
47
  require_relative "tina4/response_cache"
48
48
  require_relative "tina4/html_element"
@@ -106,10 +106,25 @@ module Tina4
106
106
  autoload :WebSocketBackplane, File.expand_path("tina4/websocket_backplane", __dir__)
107
107
  autoload :RedisBackplane, File.expand_path("tina4/websocket_backplane", __dir__)
108
108
  autoload :NATSBackplane, File.expand_path("tina4/websocket_backplane", __dir__)
109
+ # MQTT 3.1.1 — zero-dependency IoT client (socket + pack/unpack only).
110
+ # Autoloaded, not required: an app that never talks to a broker pays nothing.
111
+ autoload :Mqtt, File.expand_path("tina4/mqtt", __dir__)
112
+ autoload :MqttError, File.expand_path("tina4/mqtt", __dir__)
113
+ autoload :MqttTimeoutError, File.expand_path("tina4/mqtt", __dir__)
114
+ autoload :MqttMessage, File.expand_path("tina4/mqtt_message", __dir__)
109
115
  autoload :Testing, File.expand_path("tina4/testing", __dir__)
110
116
  autoload :ScssCompiler, File.expand_path("tina4/scss_compiler", __dir__)
111
117
  autoload :FakeData, File.expand_path("tina4/seeder", __dir__)
112
118
  autoload :WSDL, File.expand_path("tina4/wsdl", __dir__)
119
+ # Validator is a counted framework feature and belongs on the main
120
+ # `require "tina4"` surface like every other subsystem — PHP reaches
121
+ # Tina4\Validator through composer's autoloader, Node exports Validator from
122
+ # packages/core/src/index.ts, and Python resolves tina4_python.validator as a
123
+ # submodule with no extra wiring. Ruby was the odd one out: validator.rb was
124
+ # neither required nor autoloaded, so Tina4::Validator raised NameError after a
125
+ # clean boot. Autoloaded (not eagerly required) so it still costs nothing until
126
+ # a route actually validates. (feature-recount D5)
127
+ autoload :Validator, File.expand_path("tina4/validator", __dir__)
113
128
  BANNER = <<~'BANNER'
114
129
 
115
130
  ______ _ __ __
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tina4ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.13.81
4
+ version: 3.13.82
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tina4 Team
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-07-21 00:00:00.000000000 Z
11
+ date: 2026-07-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -349,6 +349,8 @@ files:
349
349
  - lib/tina4/metrics.rb
350
350
  - lib/tina4/middleware.rb
351
351
  - lib/tina4/migration.rb
352
+ - lib/tina4/mqtt.rb
353
+ - lib/tina4/mqtt_message.rb
352
354
  - lib/tina4/orm.rb
353
355
  - lib/tina4/plan.rb
354
356
  - lib/tina4/project_index.rb
@@ -396,6 +398,7 @@ files:
396
398
  - lib/tina4/scss/tina4css/_grid.scss
397
399
  - lib/tina4/scss/tina4css/_modals.scss
398
400
  - lib/tina4/scss/tina4css/_nav.scss
401
+ - lib/tina4/scss/tina4css/_pagination.scss
399
402
  - lib/tina4/scss/tina4css/_reset.scss
400
403
  - lib/tina4/scss/tina4css/_tables.scss
401
404
  - lib/tina4/scss/tina4css/_typography.scss
@@ -416,7 +419,7 @@ files:
416
419
  - lib/tina4/session_handlers/resp_client.rb
417
420
  - lib/tina4/session_handlers/valkey_handler.rb
418
421
  - lib/tina4/shutdown.rb
419
- - lib/tina4/sql_translation.rb
422
+ - lib/tina4/sql_translator.rb
420
423
  - lib/tina4/swagger.rb
421
424
  - lib/tina4/template.rb
422
425
  - lib/tina4/templates/base.twig