recreate project
@@ -0,0 +1,597 @@
|
||||
/*!
|
||||
* Bootstrap Reboot v5.3.7 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2025 The Bootstrap Authors
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
: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, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
|
||||
--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: rgba(33, 37, 41, 0.75);
|
||||
--bs-secondary-color-rgb: 33, 37, 41;
|
||||
--bs-secondary-bg: #e9ecef;
|
||||
--bs-secondary-bg-rgb: 233, 236, 239;
|
||||
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
|
||||
--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: rgba(0, 0, 0, 0.175);
|
||||
--bs-border-radius: 0.375rem;
|
||||
--bs-border-radius-sm: 0.25rem;
|
||||
--bs-border-radius-lg: 0.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 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
||||
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
|
||||
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
|
||||
--bs-focus-ring-width: 0.25rem;
|
||||
--bs-focus-ring-opacity: 0.25;
|
||||
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
|
||||
--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] {
|
||||
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: rgba(222, 226, 230, 0.75);
|
||||
--bs-secondary-color-rgb: 222, 226, 230;
|
||||
--bs-secondary-bg: #343a40;
|
||||
--bs-secondary-bg-rgb: 52, 58, 64;
|
||||
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
|
||||
--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: rgba(255, 255, 255, 0.15);
|
||||
--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 {
|
||||
margin: 0;
|
||||
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: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 1rem 0;
|
||||
color: inherit;
|
||||
border: 0;
|
||||
border-top: var(--bs-border-width) solid;
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
h6, h5, h4, h3, h2, h1 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
color: var(--bs-heading-color);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: calc(1.375rem + 1.5vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: calc(1.325rem + 0.9vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h2 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: calc(1.3rem + 0.6vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h3 {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h4 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
abbr[title] {
|
||||
-webkit-text-decoration: underline dotted;
|
||||
text-decoration: underline dotted;
|
||||
cursor: help;
|
||||
-webkit-text-decoration-skip-ink: none;
|
||||
text-decoration-skip-ink: none;
|
||||
}
|
||||
|
||||
address {
|
||||
margin-bottom: 1rem;
|
||||
font-style: normal;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
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: 0.5rem;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
mark {
|
||||
padding: 0.1875em;
|
||||
color: var(--bs-highlight-color);
|
||||
background-color: var(--bs-highlight-bg);
|
||||
}
|
||||
|
||||
sub,
|
||||
sup {
|
||||
position: relative;
|
||||
font-size: 0.75em;
|
||||
line-height: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
|
||||
text-decoration: underline;
|
||||
}
|
||||
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 {
|
||||
display: block;
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
overflow: auto;
|
||||
font-size: 0.875em;
|
||||
}
|
||||
pre code {
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 0.875em;
|
||||
color: var(--bs-code-color);
|
||||
word-wrap: break-word;
|
||||
}
|
||||
a > code {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
kbd {
|
||||
padding: 0.1875rem 0.375rem;
|
||||
font-size: 0.875em;
|
||||
color: var(--bs-body-bg);
|
||||
background-color: var(--bs-body-color);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
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 {
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
color: var(--bs-secondary-color);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
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 {
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
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 {
|
||||
padding: 0;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
float: left;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
line-height: inherit;
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
legend {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
legend + * {
|
||||
clear: left;
|
||||
}
|
||||
|
||||
::-webkit-datetime-edit-fields-wrapper,
|
||||
::-webkit-datetime-edit-text,
|
||||
::-webkit-datetime-edit-minute,
|
||||
::-webkit-datetime-edit-hour-field,
|
||||
::-webkit-datetime-edit-day-field,
|
||||
::-webkit-datetime-edit-month-field,
|
||||
::-webkit-datetime-edit-year-field {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-inner-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
[type=search] {
|
||||
-webkit-appearance: textfield;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
/* rtl:raw:
|
||||
[type="tel"],
|
||||
[type="url"],
|
||||
[type="email"],
|
||||
[type="number"] {
|
||||
direction: ltr;
|
||||
}
|
||||
*/
|
||||
::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
::-webkit-color-swatch-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
::file-selector-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
output {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
iframe {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=bootstrap-reboot.css.map */
|
||||
@@ -0,0 +1,594 @@
|
||||
/*!
|
||||
* Bootstrap Reboot v5.3.7 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2025 The Bootstrap Authors
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
: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, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
|
||||
--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: rgba(33, 37, 41, 0.75);
|
||||
--bs-secondary-color-rgb: 33, 37, 41;
|
||||
--bs-secondary-bg: #e9ecef;
|
||||
--bs-secondary-bg-rgb: 233, 236, 239;
|
||||
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
|
||||
--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: rgba(0, 0, 0, 0.175);
|
||||
--bs-border-radius: 0.375rem;
|
||||
--bs-border-radius-sm: 0.25rem;
|
||||
--bs-border-radius-lg: 0.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 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
||||
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
|
||||
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
|
||||
--bs-focus-ring-width: 0.25rem;
|
||||
--bs-focus-ring-opacity: 0.25;
|
||||
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
|
||||
--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] {
|
||||
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: rgba(222, 226, 230, 0.75);
|
||||
--bs-secondary-color-rgb: 222, 226, 230;
|
||||
--bs-secondary-bg: #343a40;
|
||||
--bs-secondary-bg-rgb: 52, 58, 64;
|
||||
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
|
||||
--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: rgba(255, 255, 255, 0.15);
|
||||
--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 {
|
||||
margin: 0;
|
||||
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: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 1rem 0;
|
||||
color: inherit;
|
||||
border: 0;
|
||||
border-top: var(--bs-border-width) solid;
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
h6, h5, h4, h3, h2, h1 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
color: var(--bs-heading-color);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: calc(1.375rem + 1.5vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: calc(1.325rem + 0.9vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h2 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: calc(1.3rem + 0.6vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h3 {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h4 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
abbr[title] {
|
||||
-webkit-text-decoration: underline dotted;
|
||||
text-decoration: underline dotted;
|
||||
cursor: help;
|
||||
-webkit-text-decoration-skip-ink: none;
|
||||
text-decoration-skip-ink: none;
|
||||
}
|
||||
|
||||
address {
|
||||
margin-bottom: 1rem;
|
||||
font-style: normal;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul {
|
||||
padding-right: 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: 0.5rem;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
mark {
|
||||
padding: 0.1875em;
|
||||
color: var(--bs-highlight-color);
|
||||
background-color: var(--bs-highlight-bg);
|
||||
}
|
||||
|
||||
sub,
|
||||
sup {
|
||||
position: relative;
|
||||
font-size: 0.75em;
|
||||
line-height: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
|
||||
text-decoration: underline;
|
||||
}
|
||||
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 {
|
||||
display: block;
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
overflow: auto;
|
||||
font-size: 0.875em;
|
||||
}
|
||||
pre code {
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 0.875em;
|
||||
color: var(--bs-code-color);
|
||||
word-wrap: break-word;
|
||||
}
|
||||
a > code {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
kbd {
|
||||
padding: 0.1875rem 0.375rem;
|
||||
font-size: 0.875em;
|
||||
color: var(--bs-body-bg);
|
||||
background-color: var(--bs-body-color);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
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 {
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
color: var(--bs-secondary-color);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
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 {
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
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 {
|
||||
padding: 0;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
float: right;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
line-height: inherit;
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
legend {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
legend + * {
|
||||
clear: right;
|
||||
}
|
||||
|
||||
::-webkit-datetime-edit-fields-wrapper,
|
||||
::-webkit-datetime-edit-text,
|
||||
::-webkit-datetime-edit-minute,
|
||||
::-webkit-datetime-edit-hour-field,
|
||||
::-webkit-datetime-edit-day-field,
|
||||
::-webkit-datetime-edit-month-field,
|
||||
::-webkit-datetime-edit-year-field {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-inner-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
[type=search] {
|
||||
-webkit-appearance: textfield;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
[type="tel"],
|
||||
[type="url"],
|
||||
[type="email"],
|
||||
[type="number"] {
|
||||
direction: ltr;
|
||||
}
|
||||
::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
::-webkit-color-swatch-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
::file-selector-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
output {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
iframe {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
/*# sourceMappingURL=bootstrap-reboot.rtl.css.map */
|
||||
@@ -0,0 +1,443 @@
|
||||
/* ========== RESET & BASE LAYOUT ========== */
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
flex: 1;
|
||||
padding-bottom: 50px;
|
||||
/* adds space above footer */
|
||||
}
|
||||
|
||||
/* ========== CONTAINER & FLEX LAYOUT ========== */
|
||||
/* Avoid overriding Bootstrap's .container-fluid globally, which can break layouts on laptops.
|
||||
If a full-height flex container is needed, prefer a dedicated utility class (e.g., .app-flex). */
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
padding: 5px;
|
||||
background-color: #f1f3f4;
|
||||
overflow-x: hidden;
|
||||
padding-bottom: 50px;
|
||||
/* <-- add this */
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 250px;
|
||||
background-color: #fff;
|
||||
padding-top: 10px;
|
||||
border-right: 1px solid #ddd;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
height: 100vh;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.sidebar a {
|
||||
display: block;
|
||||
color: #333;
|
||||
padding: 5px;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.sidebar a:hover {
|
||||
background-color: #f1f1f1;
|
||||
color: #007bff;
|
||||
}
|
||||
|
||||
.sidebar a.active {
|
||||
background-color: #007bff;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* ========== FOOTER ========== */
|
||||
.footer {
|
||||
background-color: #f1f1f1;
|
||||
padding: 5px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
margin-top: auto;
|
||||
font-size: 16px;
|
||||
position: relative;
|
||||
/* prevents overlap */
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* ========== TABLE STYLING ========== */
|
||||
.table-responsive {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
display: block;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 20px;
|
||||
font-size: 1rem;
|
||||
table-layout: auto;
|
||||
/* Allow tables to shrink on smaller screens; the responsive wrapper will handle overflow when needed. */
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
table,
|
||||
th,
|
||||
td {
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: 5px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* Keep headers compact but allow cell content to wrap for better adaptability */
|
||||
th { white-space: nowrap; }
|
||||
td {
|
||||
white-space: normal;
|
||||
overflow-wrap: anywhere; /* wrap long words/URLs/emails */
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
thead th {
|
||||
background-color: lightgray;
|
||||
color: black;
|
||||
}
|
||||
|
||||
/* ========== ALIGNMENT HELPERS ========== */
|
||||
.text-left {
|
||||
text-align: left !important;
|
||||
}
|
||||
|
||||
.right-align {
|
||||
text-align: right !important;
|
||||
}
|
||||
|
||||
/* ========== DASHBOARD TITLE ========== */
|
||||
.dashboard-title {
|
||||
/*border-bottom: 4px solid #007bff; */
|
||||
padding-bottom: 10px;
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.dashboard-title h2 {
|
||||
margin: 0;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
/* ========== FORMS & BUTTONS ========== */
|
||||
.disabled-select,
|
||||
.disabled-checkbox {
|
||||
background-color: #e9ecef;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.custom-button {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.support-form {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 5px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 8px;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
#message {
|
||||
height: 350px;
|
||||
}
|
||||
|
||||
/* ========== MESSAGES ========== */
|
||||
.message {
|
||||
text-align: center;
|
||||
margin-top: 20%;
|
||||
}
|
||||
|
||||
.message img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.message-list,
|
||||
.message-form-container {
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
|
||||
/* ========== CALENDAR ========== */
|
||||
#calendar-container {
|
||||
width: 100%;
|
||||
padding: 5px;
|
||||
overflow-x: auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#calendar {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 5px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* FullCalendar force 100% width */
|
||||
.fc {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.fc-scrollgrid,
|
||||
.fc-view,
|
||||
.fc-daygrid,
|
||||
.fc-daygrid-body {
|
||||
width: 100% !important;
|
||||
box-sizing: border-box;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.fc-toolbar {
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.fc-toolbar h2 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.fc-toolbar {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.fc-toolbar h2 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.fc-col-header-cell,
|
||||
.fc-daygrid-day-number,
|
||||
.fc-event {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.fc-daygrid-day-frame {
|
||||
padding: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*================DISPLAY ERROR RECTANGLE================*/
|
||||
.alert-error {
|
||||
background-color: #f8d7da;
|
||||
border: 1px solid #f5c2c7;
|
||||
color: #842029;
|
||||
padding: 5px;
|
||||
border-radius: 4px;
|
||||
margin: 20px 0;
|
||||
font-weight: bold;
|
||||
font-size: 1rem;
|
||||
box-shadow: 0 0 5px rgba(255, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
|
||||
/*================NAVBAR================*/
|
||||
.custom-navbar {
|
||||
background: linear-gradient(to right,
|
||||
#ffffff 0%, /* almost white */
|
||||
#d8f5d0 40%,
|
||||
#9cd88f 100%
|
||||
);
|
||||
color: #080808;
|
||||
|
||||
padding-top: 0.3rem;
|
||||
padding-bottom: 0.3rem;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1050;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
/* Toggler icon override */
|
||||
.navbar-toggler {
|
||||
background-color: white;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
/* Ensure proper spacing for nav links */
|
||||
.nav-link {
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Fix collapse dropdown for mobile */
|
||||
@media (max-width: 991.98px) {
|
||||
.navbar-collapse {
|
||||
background: linear-gradient(to right,
|
||||
#c6f7c5 0%,
|
||||
#b6d490 30%,
|
||||
#359f04 100%);
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1051;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.navbar .btn {
|
||||
margin-top: 1rem;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.navbar-logo {
|
||||
height: 80px !important;
|
||||
width: auto !important;
|
||||
object-fit: contain;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
|
||||
.logout-btn {
|
||||
font-weight: bold;
|
||||
padding: 0.25rem 0.75rem; /* smaller height */
|
||||
font-size: 0.95rem; /* slightly smaller text */
|
||||
line-height: 1.2; /* tighter vertical alignment */
|
||||
}
|
||||
|
||||
|
||||
/* ================= PROFILE DROPDOWN ================= */
|
||||
|
||||
.account-btn {
|
||||
background-color: #28a745;
|
||||
border: none;
|
||||
color: #fff;
|
||||
border-radius: 50%;
|
||||
width: 40px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dropdown-menu-custom {
|
||||
right: 0;
|
||||
left: auto;
|
||||
transform: translateX(-75%);
|
||||
width: 1px !important;
|
||||
max-width: 300px !important;
|
||||
background-color: chartreuse;
|
||||
}
|
||||
|
||||
.profile-container {
|
||||
text-align: center;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.profile-picture {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
background-color: #f0f0f0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin: 0 auto 20px auto;
|
||||
color: rgb(6, 126, 14);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.profile-details {
|
||||
padding: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.sign-out-btn {
|
||||
width: 100%;
|
||||
padding: 10px 20px;
|
||||
border: 2px solid #2c9b09;
|
||||
background-color: #2c9b09;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.dropdown-hover:hover .dropdown-menu {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.top-right {
|
||||
position: fixed;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
min-width: 200px;
|
||||
}
|
||||
.menu-title{
|
||||
color: rgba(248, 76, 8, 0.842)
|
||||
}
|
||||
|
||||
.flash-error {
|
||||
animation: flash 1.2s ease-in-out;
|
||||
border: 2px solid red !important;
|
||||
}
|
||||
|
||||
@keyframes flash {
|
||||
0% { background-color: #ffe6e6; }
|
||||
50% { background-color: #ffcccc; }
|
||||
100% { background-color: #fff; }
|
||||
}
|
||||
|
||||
.is-invalid {
|
||||
border-color: red !important;
|
||||
}
|
||||
.invalid-feedback {
|
||||
color: red;
|
||||
font-size: 0.875rem;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.invalid-feedback {
|
||||
color: red;
|
||||
font-size: 0.875em;
|
||||
display: none;
|
||||
}
|
||||
.is-invalid {
|
||||
border-color: red;
|
||||
}
|
||||
@@ -0,0 +1,332 @@
|
||||
/* Inside /css/landing_page.css */
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/* Force Arial font in hero welcome */
|
||||
.welcome-message,
|
||||
.welcome-message h1,
|
||||
.welcome-message h2,
|
||||
.welcome-message h3,
|
||||
.welcome-message h4 {
|
||||
font-family: Arial, sans-serif !important;
|
||||
font-style: normal !important;
|
||||
font-weight: normal !important;
|
||||
text-shadow: none !important;
|
||||
}
|
||||
|
||||
.hero-section {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
color: white;
|
||||
position: relative;
|
||||
margin-bottom: 20px;
|
||||
overflow: hidden;
|
||||
height: 500px;
|
||||
/* Adjust the height as needed */
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
/* Ensures the text stands out against the background */
|
||||
}
|
||||
|
||||
.custom-hero-image {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
opacity: 0.5;
|
||||
/* Adjust to make sure the background image doesn't overshadow the text */
|
||||
}
|
||||
|
||||
.welcome-message {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: #000000;
|
||||
font: normal;
|
||||
/* White text for better contrast */
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
|
||||
/* Add shadow for text clarity */
|
||||
}
|
||||
|
||||
.welcome-message h1 {
|
||||
font-size: 4rem;
|
||||
margin-bottom: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
/* Adds space after the <h1> */
|
||||
}
|
||||
|
||||
.welcome-message h2 {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
/* Adds space after the <h1> */
|
||||
}
|
||||
|
||||
.welcome-message p {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 2rem;
|
||||
/* Adds space after the <h1> */
|
||||
}
|
||||
|
||||
.hero-section .overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(46, 46, 46, 0.5);
|
||||
/* Adjust the opacity as needed */
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.hero-section .slideshow img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.hero-section .welcome-message {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.navigation,
|
||||
aside {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.navigation div,
|
||||
aside div {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
background-color: #2c9b09;
|
||||
/* Changed background color */
|
||||
color: #fff;
|
||||
padding: 20px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
color: #fff;
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.footer .social-media img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.dropdown-content a {
|
||||
color: black !important;
|
||||
display: block;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.dropdown-content a:hover {
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
.dropbtn {
|
||||
background-color: #343a40;
|
||||
color: white;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dropbtn:hover,
|
||||
.dropbtn:focus {
|
||||
background-color: #23272b;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.dropdown-content {
|
||||
display: none;
|
||||
position: absolute;
|
||||
background-color: white;
|
||||
min-width: 160px;
|
||||
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.dropdown:hover .dropdown-content {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.navbar-brand img {
|
||||
height: 40px;
|
||||
/* Adjust height as needed */
|
||||
}
|
||||
|
||||
.secondary-header {
|
||||
background-color: #f8f9fa;
|
||||
padding: 10px 20px;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.secondary-header .dropdown {
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.secondary-header .dropbtn {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.secondary-header .dropbtn:hover,
|
||||
.secondary-header .dropbtn:focus {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
.custom-hero-image {
|
||||
width: 100%;
|
||||
/* Adjust the width as needed */
|
||||
height: 600px;
|
||||
/* Adjust the height as needed */
|
||||
object-fit: fill;
|
||||
/* Ensures the image covers the whole section */
|
||||
}
|
||||
|
||||
/* Ensure header and header are properly positioned */
|
||||
header,
|
||||
.header-menu {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.hero-section img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.footer {
|
||||
/* base footer style; final colors come from .custom-footer override */
|
||||
padding: 20px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Custom styles for the navbar */
|
||||
.navbar-nav .nav-item {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.navbar-nav .dropdown-menu {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
z-index: 1000;
|
||||
float: left;
|
||||
min-width: 10rem;
|
||||
padding: .5rem 0;
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
color: #212529;
|
||||
text-align: left;
|
||||
list-style: none;
|
||||
background-color: #fff;
|
||||
border: 1px solid rgba(0, 0, 0, .15);
|
||||
border-radius: .25rem;
|
||||
}
|
||||
|
||||
.navbar-nav .nav-item:hover .dropdown-menu {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.dropdown-toggle::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Legacy helper; if used anywhere, adopt selected menu palette */
|
||||
.navbar-custom {
|
||||
background-color: var(--mgmt-menu-bg) !important;
|
||||
color: var(--mgmt-menu-text) !important;
|
||||
}
|
||||
|
||||
/* Footer follows selected menu palette like admin */
|
||||
.custom-footer {
|
||||
background-color: var(--mgmt-menu-bg) !important;
|
||||
color: var(--mgmt-menu-text) !important;
|
||||
}
|
||||
|
||||
.custom-footer a { color: var(--mgmt-menu-text) !important; }
|
||||
|
||||
.custom-footer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/*footer*/
|
||||
.custom-footer .info-list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.custom-footer .info-list li {
|
||||
margin-bottom: .35rem;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
/* titles */
|
||||
.custom-footer .info-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: .35rem;
|
||||
}
|
||||
|
||||
/* PDF links: inline-block on small (so they center naturally),
|
||||
block + full width on md+ (so text-align: right works) */
|
||||
.custom-footer .pdf-link {
|
||||
display: inline-block;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.custom-footer .pdf-link {
|
||||
/*border-left: 1px dashed red;*/
|
||||
display: block;
|
||||
/* make it fill the container */
|
||||
width: 100%;
|
||||
/* ensure full width for alignment */
|
||||
text-align: right;
|
||||
/* align text to the right */
|
||||
}
|
||||
|
||||
.custom-footer .info-title {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
/* icon baseline alignment */
|
||||
.custom-footer i.fa,
|
||||
.custom-footer i.fas {
|
||||
width: 18px;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ensure top alignment of the row */
|
||||
.custom-footer .row {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
/* remove leftover p margins in footer */
|
||||
.custom-footer p {
|
||||
margin: 0;
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
body,
|
||||
html {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #90EE90;
|
||||
/* Light green background */
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.login-container {
|
||||
max-width: 500px;
|
||||
width: 100%;
|
||||
background-color: #ffffff;
|
||||
padding: 60px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 100px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.form-group input {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
text-align: left;
|
||||
/* Align text inside the input to the left */
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.form-actions button {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
background-color: #008000;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.form-actions button:hover {
|
||||
background-color: #2a27cf;
|
||||
}
|
||||
|
||||
.forgot-password {
|
||||
margin-top: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.forgot-password a {
|
||||
color: #008000;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.forgot-password a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: red;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/* Navbar styles */
|
||||
.navbar {
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.navbar-nav .nav-link {
|
||||
line-height: 48px;
|
||||
}
|
||||
|
||||
.navbar-nav .nav-item .nav-link:hover {
|
||||
color: orange !important;
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
.navbar-nav .dropdown-menu {
|
||||
width: auto;
|
||||
min-width: 207px;
|
||||
max-width: 100%;
|
||||
padding: 5px;
|
||||
white-space: nowrap;
|
||||
overflow-y: auto;
|
||||
box-sizing: border-box;
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
.navbar-nav .dropdown-menu .dropdown-item {
|
||||
padding: 8px 10px;
|
||||
white-space: nowrap;
|
||||
text-align: left;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Dropdown on large screens */
|
||||
@media (min-width: 992px) {
|
||||
.navbar-nav .dropdown:hover .dropdown-menu {
|
||||
display: block;
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Responsive dropdown for smaller screens */
|
||||
@media (max-width: 991px) {
|
||||
.navbar-nav .dropdown:hover .dropdown-menu {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.navbar-nav .dropdown-menu {
|
||||
position: static;
|
||||
float: none;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.navbar-nav .dropdown-item {
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
/* Top right positioning */
|
||||
.top-right {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
/* Dropdown hover container */
|
||||
.dropdown-hover {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.dropdown-hover .dropdown-menu {
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
background-color: white;
|
||||
min-width: 160px;
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.dropdown-hover:hover .dropdown-menu {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Profile picture style */
|
||||
.profile-picture {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background-color: #6c757d;
|
||||
color: white;
|
||||
text-align: center;
|
||||
line-height: 40px;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Dropdown items */
|
||||
.dropdown-item {
|
||||
color: black;
|
||||
padding: 10px;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.dropdown-item:hover {
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
|
||||
.sign-out-btn {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* Form and body styling */
|
||||
body {
|
||||
background-color: #ffffff;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.form-control,
|
||||
.btn,
|
||||
.alert {
|
||||
background-color: #f8f9fa;
|
||||
color: #000000;
|
||||
border: 1px solid #ced4da;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
background-color: #e9ecef;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
label,
|
||||
h1 {
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #007bff;
|
||||
border-color: #007bff;
|
||||
color: #ffffff;
|
||||
}
|
||||
.card-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.card-text {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
.circle-box {
|
||||
position: relative;
|
||||
width: 220px;
|
||||
height: 220px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 16px;
|
||||
color: #fff;
|
||||
font-size: 0.95rem;
|
||||
box-shadow: 0 6px 14px rgba(0, 0, 0, 0.2);
|
||||
transition: transform 0.3s ease;
|
||||
text-align: center;
|
||||
margin: 0 auto;
|
||||
z-index: 1;
|
||||
/* Allow Bootstrap bg-* utilities to control color per card */
|
||||
/* background-color intentionally not set here */
|
||||
}
|
||||
|
||||
.circle-box:hover {
|
||||
transform: scale(1.07);
|
||||
}
|
||||
|
||||
/* Ensure readable text even if Bootstrap text-dark was used in markup */
|
||||
/* Respect text utility classes (let text-dark render dark text when present) */
|
||||
|
||||
/* Headings and subtext inside the circle */
|
||||
.circle-box h4 { color: #fff; margin-bottom: .25rem; }
|
||||
.circle-box small { color: rgba(255,255,255,0.9); }
|
||||
|
||||
.hover-popup {
|
||||
display: none;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background-color: white;
|
||||
color: black;
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
width: 220px;
|
||||
z-index: 9999; /* make sure it's above all */
|
||||
font-size: 0.85rem;
|
||||
margin-top: 10px;
|
||||
text-align: left;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.circle-box:hover .hover-popup {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 992px) {
|
||||
.circle-box { width: 160px; height: 160px; }
|
||||
.hover-popup { width: 200px; }
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.circle-box { width: 120px; height: 120px; font-size: 0.85rem; }
|
||||
.hover-popup { width: 180px; font-size: 0.8rem; }
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
body {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
border-bottom: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal-header .logo {
|
||||
width: 100%;
|
||||
max-width: 200px;
|
||||
height: auto;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.modal-body h1 {
|
||||
font-size: 1.8em;
|
||||
color: #28a745;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.modal-body p {
|
||||
font-size: 1rem;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.modal-content {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.modal-body h1 {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.modal-body p {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.modal-header .logo {
|
||||
max-width: 150px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,626 @@
|
||||
:root {
|
||||
--primary-color: #2e7d32;
|
||||
--secondary-color: #4caf50;
|
||||
--accent-color: #1b5e20;
|
||||
--light-bg: #f9f9f9;
|
||||
--text-color: #333;
|
||||
--container-bg: #fff;
|
||||
--shadow: 0 4px 20px rgba(0, 0, 0, 0.12);
|
||||
--border-radius: 10px;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background-color: var(--light-bg);
|
||||
color: var(--text-color);
|
||||
line-height: 1.7;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
width: 100%;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: linear-gradient(135deg, var(--primary-color), var(--accent-color));
|
||||
color: white;
|
||||
padding: 20px 15px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
max-width: none;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.logo-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.school-name {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.header-info {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.main-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
max-width: none;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
gap: 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mobile-nav-toggle {
|
||||
display: block;
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
padding: 12px 15px;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
margin-bottom: 15px;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.mobile-nav-toggle i {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
/* Mobile modal styles - positioned relative to viewport, not parent */
|
||||
/* Hide desktop nav on mobile */
|
||||
@media (max-width: 991px) {
|
||||
.desktop-nav {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Hide mobile nav on desktop */
|
||||
@media (min-width: 992px) {
|
||||
#policyNav {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
.policy-nav {
|
||||
display: none !important;
|
||||
background: white !important;
|
||||
border-radius: 0 !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
box-shadow: var(--shadow) !important;
|
||||
width: 100vw !important;
|
||||
height: 100vh !important;
|
||||
max-width: 100vw !important;
|
||||
max-height: 100vh !important;
|
||||
min-width: 100vw !important;
|
||||
min-height: 100vh !important;
|
||||
position: fixed !important;
|
||||
top: 0 !important;
|
||||
left: 0 !important;
|
||||
right: 0 !important;
|
||||
bottom: 0 !important;
|
||||
z-index: 99999 !important;
|
||||
overflow-y: auto !important;
|
||||
overflow-x: hidden !important;
|
||||
box-sizing: border-box !important;
|
||||
transform: none !important;
|
||||
border: none !important;
|
||||
/* Break out of parent container */
|
||||
margin-left: calc(-50vw + 50%) !important;
|
||||
margin-right: calc(-50vw + 50%) !important;
|
||||
}
|
||||
|
||||
.policy-nav.active {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
}
|
||||
|
||||
/* Content wrapper inside modal */
|
||||
.policy-nav > * {
|
||||
padding: 20px 15px !important;
|
||||
width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
/* Navigation list */
|
||||
.policy-nav ul {
|
||||
list-style: none !important;
|
||||
flex: 1 !important;
|
||||
min-height: calc(100vh - 120px) !important;
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
justify-content: flex-start !important;
|
||||
width: 100% !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 15px 20px 15px !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
/* Ensure nav title has proper spacing */
|
||||
.policy-nav .nav-title {
|
||||
margin-top: 50px !important;
|
||||
margin-bottom: 20px !important;
|
||||
padding: 0 15px !important;
|
||||
}
|
||||
|
||||
/* Close button positioning */
|
||||
.nav-close {
|
||||
position: fixed !important;
|
||||
top: 15px !important;
|
||||
right: 15px !important;
|
||||
z-index: 100000 !important;
|
||||
}
|
||||
|
||||
/* Hide other content when modal is active */
|
||||
body.modal-open .header,
|
||||
body.modal-open .footer,
|
||||
body.modal-open .policy-content {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Ensure modal covers everything */
|
||||
body.modal-open .policy-nav.active {
|
||||
position: fixed !important;
|
||||
top: 0 !important;
|
||||
left: 0 !important;
|
||||
width: 100vw !important;
|
||||
height: 100vh !important;
|
||||
z-index: 999999 !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Close button for mobile modal */
|
||||
.nav-close {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
font-size: 1.2rem;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
/* Adjust nav title spacing for mobile modal */
|
||||
.policy-nav .nav-title {
|
||||
margin-top: 50px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* Prevent body scroll when modal is open */
|
||||
body.modal-open {
|
||||
overflow: hidden !important;
|
||||
position: fixed !important;
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
font-weight: 700;
|
||||
margin-bottom: 15px;
|
||||
color: var(--primary-color);
|
||||
font-size: 1.2rem;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 2px solid var(--secondary-color);
|
||||
}
|
||||
|
||||
.policy-nav ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.policy-nav li {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.policy-nav a {
|
||||
color: var(--text-color);
|
||||
text-decoration: none;
|
||||
transition: all 0.2s;
|
||||
display: block;
|
||||
padding: 10px 12px;
|
||||
border-left: 4px solid transparent;
|
||||
border-radius: 4px;
|
||||
background: #f8f8f8;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.policy-nav a:hover,
|
||||
.policy-nav a.active {
|
||||
color: var(--primary-color);
|
||||
border-left-color: var(--primary-color);
|
||||
background: #e8f5e9;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.policy-content {
|
||||
background: white;
|
||||
border-radius: var(--border-radius);
|
||||
padding: 25px 20px;
|
||||
box-shadow: var(--shadow);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.policy-header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 2px solid var(--primary-color);
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--primary-color);
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 10px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.last-updated {
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
margin-top: 8px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 35px;
|
||||
scroll-margin-top: 15px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: var(--secondary-color);
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
h2 i {
|
||||
margin-right: 12px;
|
||||
color: var(--accent-color);
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
color: var(--accent-color);
|
||||
font-size: 1.0rem;
|
||||
margin: 25px 0 15px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
h3 i {
|
||||
margin-right: 10px;
|
||||
font-size: 1.0rem;
|
||||
}
|
||||
|
||||
.policy-body {
|
||||
text-align: left;
|
||||
margin-bottom: 10px;
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.policy-body p {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.policy-body ul {
|
||||
margin: 15px 0;
|
||||
padding-left: 25px;
|
||||
}
|
||||
|
||||
.policy-body li {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 20px 0;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
table,
|
||||
th,
|
||||
td {
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: 14px 16px;
|
||||
text-align: left;
|
||||
white-space: normal;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
tr:nth-child(even) {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
background-color: #f1f8e9;
|
||||
}
|
||||
|
||||
.actions-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 15px;
|
||||
margin-top: 40px;
|
||||
padding-top: 25px;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 14px 20px;
|
||||
background-color: var(--primary-color);
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-size: 13px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.action-btn:hover {
|
||||
background-color: var(--accent-color);
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.action-btn.print {
|
||||
background-color: #555;
|
||||
}
|
||||
|
||||
.action-btn.print:hover {
|
||||
background-color: #333;
|
||||
}
|
||||
|
||||
.footer {
|
||||
background: linear-gradient(135deg, var(--accent-color), var(--primary-color));
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 20px 15px;
|
||||
margin-top: 30px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.footer p {
|
||||
margin-bottom: 8px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
margin-bottom: 20px;
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.breadcrumb a {
|
||||
color: var(--primary-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.breadcrumb a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
margin-bottom: 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.search-box input {
|
||||
width: 100%;
|
||||
padding: 12px 45px 12px 15px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 30px;
|
||||
font-size: 13px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.search-box i {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.policy-image {
|
||||
max-width: none;
|
||||
height: auto;
|
||||
border: 1px solid #ddd;
|
||||
margin: 15px 0;
|
||||
display: block;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/* Tablet styles */
|
||||
@media (min-width: 768px) and (max-width: 991px) {
|
||||
.policy-nav a {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.actions-container {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
flex: 1;
|
||||
min-width: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Desktop styles */
|
||||
@media (min-width: 992px) {
|
||||
.header {
|
||||
padding: 25px 40px;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.logo-container {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.mobile-nav-toggle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.main-container {
|
||||
flex-direction: row;
|
||||
padding: 25px;
|
||||
gap: 30px;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
/* Override mobile modal styles for desktop */
|
||||
.policy-nav {
|
||||
display: block !important;
|
||||
position: static !important;
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
flex: 0 0 280px;
|
||||
max-height: calc(150vh - 50px);
|
||||
position: sticky !important;
|
||||
top: 25px;
|
||||
align-self: flex-start;
|
||||
border-radius: var(--border-radius) !important;
|
||||
z-index: auto !important;
|
||||
padding: 20px 15px !important;
|
||||
}
|
||||
|
||||
.policy-nav ul {
|
||||
flex: none !important;
|
||||
min-height: auto !important;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.policy-content {
|
||||
flex: 1;
|
||||
padding: 35px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.actions-container {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
/* Hide close button on desktop */
|
||||
.nav-close {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Reset nav title margin on desktop */
|
||||
.policy-nav .nav-title {
|
||||
margin-top: 15px !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media print {
|
||||
|
||||
.policy-nav,
|
||||
.actions-container,
|
||||
.header,
|
||||
.footer,
|
||||
.mobile-nav-toggle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.main-container {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.policy-content {
|
||||
box-shadow: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: white;
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
body,
|
||||
html {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
background-color: #deffe1;
|
||||
/* original background */
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
/* Optional centering for login-specific pages */
|
||||
body.centered {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #90EE90;
|
||||
/* optional light green for login */
|
||||
}
|
||||
|
||||
/* Registration form */
|
||||
.registration-form {
|
||||
padding: 50px 0;
|
||||
}
|
||||
|
||||
.registration-form form {
|
||||
max-width: 600px;
|
||||
width: 100%;
|
||||
background-color: #ffffff;
|
||||
padding: 60px;
|
||||
border-radius: 30px;
|
||||
margin: auto;
|
||||
box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.075);
|
||||
}
|
||||
|
||||
.registration-form .form-icon {
|
||||
background-color: #fefefef6;
|
||||
border-radius: 50%;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
margin: auto auto 50px auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.registration-form .form-icon img {
|
||||
width: 60%;
|
||||
height: 60%;
|
||||
object-fit: contain;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.registration-form .item {
|
||||
border-radius: 20px;
|
||||
margin-bottom: 25px;
|
||||
padding: 10px 20px;
|
||||
}
|
||||
|
||||
.registration-form .create-account {
|
||||
border-radius: 30px;
|
||||
padding: 10px 20px;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
background-color: #099909;
|
||||
border: none;
|
||||
color: white;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.home-button {
|
||||
border-radius: 30px;
|
||||
padding: 10px 20px;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
background-color: #6d7179;
|
||||
border: none;
|
||||
color: white;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.registration-form .social-media {
|
||||
max-width: 600px;
|
||||
background-color: #fff;
|
||||
margin: auto;
|
||||
padding: 35px 0;
|
||||
text-align: center;
|
||||
border-radius: 30px;
|
||||
color: #9fadca;
|
||||
border-top: 1px solid #dee9ff;
|
||||
box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.075);
|
||||
}
|
||||
|
||||
.registration-form .social-icons {
|
||||
margin-top: 30px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.registration-form .social-icons a {
|
||||
font-size: 23px;
|
||||
margin: 0 3px;
|
||||
color: #5691ff;
|
||||
border: 1px solid;
|
||||
border-radius: 50%;
|
||||
width: 45px;
|
||||
display: inline-block;
|
||||
height: 45px;
|
||||
text-align: center;
|
||||
background-color: #fff;
|
||||
line-height: 45px;
|
||||
}
|
||||
|
||||
.registration-form .social-icons a:hover {
|
||||
text-decoration: none;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
/* Login container */
|
||||
.login-container {
|
||||
max-width: 500px;
|
||||
width: 100%;
|
||||
background-color: #ffffff;
|
||||
padding: 60px;
|
||||
border-radius: 30px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 200px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* Form elements */
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.form-group input {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.form-actions button {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
background-color: #008000;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.form-actions button:hover {
|
||||
background-color: #006400;
|
||||
}
|
||||
|
||||
.forgot-password {
|
||||
margin-top: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.forgot-password a {
|
||||
color: #008000;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.forgot-password a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Validation styles */
|
||||
.error-message {
|
||||
color: red;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
input.is-invalid,
|
||||
select.is-invalid {
|
||||
border: 2px solid #dc3545 !important;
|
||||
background-color: white !important;
|
||||
box-shadow: none !important;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.is-invalid:focus {
|
||||
box-shadow: 0 0 4px 1px rgba(220, 53, 69, 0.5);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Animation for invalid fields */
|
||||
@keyframes flashBorder {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
box-shadow: 0 0 0 2px red;
|
||||
}
|
||||
|
||||
50% {
|
||||
box-shadow: 0 0 6px 4px rgba(255, 0, 0, 0.6);
|
||||
}
|
||||
}
|
||||
|
||||
.flash-error {
|
||||
animation: flashBorder 0.8s ease-in-out 2;
|
||||
}
|
||||
|
||||
/* Responsive padding */
|
||||
@media (max-width: 576px) {
|
||||
|
||||
.registration-form form,
|
||||
.login-container {
|
||||
padding: 50px 20px;
|
||||
}
|
||||
|
||||
.registration-form .form-icon {
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
font-size: 30px;
|
||||
line-height: 70px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.input-with-icon {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.input-with-icon .form-control {
|
||||
padding-right: 2.5rem;
|
||||
}
|
||||
|
||||
.input-with-icon .toggle-password {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 0.75rem;
|
||||
transform: translateY(-50%);
|
||||
cursor: pointer;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
|
||||
.alert-danger {
|
||||
background-color: #fff3f3;
|
||||
border-color: #f5c6cb;
|
||||
}
|
||||
|
||||
.btn-outline-danger:hover {
|
||||
background-color: #dc3545;
|
||||
color: #fff;
|
||||
}
|
||||
.policy-label::after {
|
||||
content: " *";
|
||||
color: red;
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/* General styles */
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
/* Base font size */
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.left-side,
|
||||
.right-side {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.left-side {
|
||||
background-color: #fff;
|
||||
flex: 1;
|
||||
max-width: 50%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-right: 2px solid #ccc;
|
||||
}
|
||||
|
||||
.left-side .logo {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.right-side {
|
||||
flex: 1;
|
||||
max-width: 50%;
|
||||
background-color: #f7f7f7;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.success-message {
|
||||
max-width: 90%;
|
||||
font-size: 1.2rem;
|
||||
/* Increased text size */
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.success-message h1 {
|
||||
color: #4CAF50;
|
||||
font-size: 2.2rem;
|
||||
/* Larger heading */
|
||||
}
|
||||
|
||||
.success-message .highlight-email {
|
||||
font-weight: bold;
|
||||
color: #007BFF;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.success-message .important-note {
|
||||
font-weight: bold;
|
||||
color: red;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 12px 24px;
|
||||
margin-top: 25px;
|
||||
color: #fff;
|
||||
background-color: #007BFF;
|
||||
text-decoration: none;
|
||||
border-radius: 5px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
flex-direction: column;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.left-side,
|
||||
.right-side {
|
||||
max-width: 100%;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.left-side {
|
||||
border-bottom: 2px solid #ccc;
|
||||
}
|
||||
|
||||
.left-side,
|
||||
.right-side {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.success-message {
|
||||
max-width: 100%;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.success-message h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
.session-timeout-modal {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
}
|
||||
|
||||
.session-timeout-modal button {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.session-timeout-modal button:hover {
|
||||
opacity: 0.9;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.session-timeout-modal button:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.countdown {
|
||||
font-size: 1.3em;
|
||||
font-weight: bold;
|
||||
color: #e74c3c;
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
.left-side .logo {
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
/* Adjustable maximum size */
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/* Responsive adjustments for smaller screens */
|
||||
@media (max-width: 768px) {
|
||||
.left-side .logo {
|
||||
max-width: 300px;
|
||||
/* Reduce size on smaller screens */
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.left-side .logo {
|
||||
max-width: 150px;
|
||||
/* Further reduce size for very small screens */
|
||||
}
|
||||
}
|
||||
|
||||
/* General styles */
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Arial, sans-serif;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
max-width: 1200px;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.left-side {
|
||||
background-color: #fff;
|
||||
flex: 1;
|
||||
max-width: 50%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-right: 2px solid #ccc;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.left-side .logo {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.password-reset-container {
|
||||
flex: 1;
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.password-reset-container h2 {
|
||||
font-size: 1.8em;
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
font-size: 0.9rem;
|
||||
color: #555;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.form-group input {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
font-size: 1rem;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
background-color: #fff;
|
||||
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-group input:focus {
|
||||
border-color: #007bff;
|
||||
box-shadow: 0 0 5px rgba(0, 123, 255, 0.5);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
width: 100%;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.form-actions button {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
background-color: #007bff;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-actions button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
flex-direction: column;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.left-side {
|
||||
border-right: none;
|
||||
border-bottom: 2px solid #ccc;
|
||||
}
|
||||
|
||||
.left-side .logo {
|
||||
max-width: 150px;
|
||||
}
|
||||
|
||||
.password-reset-container {
|
||||
margin: 20px 0;
|
||||
max-width: 100%;
|
||||
padding: 15px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,813 @@
|
||||
/********** Template CSS **********/
|
||||
:root {
|
||||
--primary: #008000;
|
||||
--light: #FFF5F3;
|
||||
--dark: #103741;
|
||||
}
|
||||
|
||||
.text-green {
|
||||
color: #008000 !important;
|
||||
}
|
||||
|
||||
.breadcrumb-item.text-green a {
|
||||
color: #008000 !important;
|
||||
}
|
||||
|
||||
.breadcrumb-item.text-green a:hover {
|
||||
color: #006400 !important;
|
||||
}
|
||||
|
||||
.green-title {
|
||||
color: #008000;
|
||||
}
|
||||
|
||||
.back-to-top {
|
||||
position: fixed;
|
||||
display: none;
|
||||
right: 45px;
|
||||
bottom: 45px;
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
/*** Spinner ***/
|
||||
#spinner {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity .5s ease-out, visibility 0s linear .5s;
|
||||
z-index: 99999;
|
||||
}
|
||||
|
||||
#spinner.show {
|
||||
transition: opacity .5s ease-out, visibility 0s linear 0s;
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/*** Button ***/
|
||||
.btn {
|
||||
font-weight: 500;
|
||||
transition: .5s;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: white;
|
||||
border-color: #008000;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: #90EE90;
|
||||
border-color: #006400;
|
||||
}
|
||||
|
||||
.text-primary {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn.btn-primary {
|
||||
color: #008000;
|
||||
}
|
||||
|
||||
.btn-square {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
}
|
||||
|
||||
.btn-sm-square {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.btn-lg-square {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.btn-square,
|
||||
.btn-sm-square,
|
||||
.btn-lg-square {
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: normal;
|
||||
border-radius: 50px;
|
||||
}
|
||||
|
||||
.bg-light-green {
|
||||
background-color: #e8f7e8;
|
||||
border-radius: .25rem;
|
||||
}
|
||||
|
||||
/*** Heading ***/
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
.h1,
|
||||
.h2,
|
||||
.h3,
|
||||
.h4,
|
||||
.display-1,
|
||||
.display-2,
|
||||
.display-3,
|
||||
.display-4,
|
||||
.display-5,
|
||||
.display-6 {
|
||||
font-family: 'Lobster Two', cursive;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
h5,
|
||||
h6,
|
||||
.h5,
|
||||
.h6 {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.font-secondary {
|
||||
font-family: 'Lobster Two', cursive;
|
||||
}
|
||||
|
||||
/*** Navbar ***/
|
||||
.navbar .navbar-nav .nav-link {
|
||||
padding: 30px 15px;
|
||||
color: var(--dark);
|
||||
font-weight: 500;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.navbar .navbar-nav .nav-link:hover,
|
||||
.navbar .navbar-nav .nav-link.active {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.navbar.sticky-top {
|
||||
top: -100px;
|
||||
transition: .5s;
|
||||
}
|
||||
|
||||
.navbar .dropdown-toggle::after {
|
||||
border: none;
|
||||
content: "\f107";
|
||||
font-family: "Font Awesome 5 Free";
|
||||
font-weight: 900;
|
||||
vertical-align: middle;
|
||||
margin-left: 5px;
|
||||
transition: .5s;
|
||||
}
|
||||
|
||||
.navbar .dropdown-toggle[aria-expanded=true]::after {
|
||||
transform: rotate(-180deg);
|
||||
}
|
||||
|
||||
@media (max-width: 991.98px) {
|
||||
.navbar .navbar-nav .nav-link {
|
||||
margin-right: 0;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.navbar .navbar-nav {
|
||||
margin-top: 15px;
|
||||
border-top: 1px solid #EEEEEE;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
.navbar .nav-item .dropdown-menu {
|
||||
display: block;
|
||||
top: calc(100% - 15px);
|
||||
margin-top: 0;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: .5s;
|
||||
}
|
||||
|
||||
.navbar .nav-item:hover .dropdown-menu {
|
||||
top: 100%;
|
||||
visibility: visible;
|
||||
transition: .5s;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/*** Header - Fixed Version ***/
|
||||
.header-carousel::before,
|
||||
.header-carousel::after,
|
||||
.page-header::before,
|
||||
.page-header::after {
|
||||
position: absolute;
|
||||
content: "";
|
||||
width: 100%;
|
||||
left: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.header-carousel::before,
|
||||
.page-header::before {
|
||||
top: 0;
|
||||
height: 10px;
|
||||
background: url(../images/bg-header-top.png) center top repeat-x;
|
||||
}
|
||||
|
||||
.header-carousel::after,
|
||||
.page-header::after {
|
||||
height: 19px;
|
||||
bottom: 0;
|
||||
background: url(../images/bg-header-bottom.png) center top repeat-x;
|
||||
}
|
||||
|
||||
.header-carousel .position-absolute {
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, .2);
|
||||
}
|
||||
|
||||
.header-carousel .owl-nav {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 8%;
|
||||
transform: translateY(-50%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.header-carousel .owl-nav .owl-prev,
|
||||
.header-carousel .owl-nav .owl-next {
|
||||
margin: 7px 0;
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #FFFFFF;
|
||||
background: transparent;
|
||||
border: 1px solid #FFFFFF;
|
||||
border-radius: 45px;
|
||||
font-size: 22px;
|
||||
transition: .5s;
|
||||
}
|
||||
|
||||
.header-carousel .owl-nav .owl-prev:hover,
|
||||
.header-carousel .owl-nav .owl-next:hover {
|
||||
background: var(--primary);
|
||||
border-color: var(--primary);
|
||||
}
|
||||
|
||||
.page-header {
|
||||
background: linear-gradient(rgba(0, 0, 0, .2), rgba(0, 0, 0, .2)), url(../images/carousel-1.jpg) center center no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.breadcrumb-item+.breadcrumb-item::before {
|
||||
color: rgba(255, 255, 255, .5);
|
||||
}
|
||||
|
||||
/*** Header Carousel - Mobile Fix ***/
|
||||
.header-carousel {
|
||||
position: relative;
|
||||
height: 900px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header-carousel .owl-carousel-item {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.header-carousel .owl-carousel-item img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.header-carousel {
|
||||
height: 500px;
|
||||
}
|
||||
|
||||
.header-carousel .owl-carousel-item img {
|
||||
position: relative; /* Changed from absolute to relative */
|
||||
min-height: 500px; /* Ensure minimum height */
|
||||
}
|
||||
|
||||
.header-carousel .owl-nav {
|
||||
display: none; /* Optional: hide nav arrows on mobile */
|
||||
}
|
||||
|
||||
.header-carousel .position-absolute {
|
||||
padding: 20px; /* Add padding for mobile content */
|
||||
}
|
||||
|
||||
.header-carousel h1 {
|
||||
font-size: 28px !important;
|
||||
margin-bottom: 15px !important;
|
||||
}
|
||||
|
||||
.header-carousel p {
|
||||
font-size: 16px !important;
|
||||
}
|
||||
}
|
||||
|
||||
/*** Facility ***/
|
||||
.facility-item .facility-icon {
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border-radius: 100px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.facility-item .facility-icon::before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: rgba(255, 255, 255, .9);
|
||||
transition: .5s;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.facility-item .facility-icon span {
|
||||
position: absolute;
|
||||
content: "";
|
||||
width: 15px;
|
||||
height: 30px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.facility-item .facility-icon span:last-child {
|
||||
left: auto;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.facility-item .facility-icon i {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.facility-item .facility-text {
|
||||
position: relative;
|
||||
min-height: 250px;
|
||||
padding: 30px;
|
||||
border-radius: 100%;
|
||||
display: flex;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.facility-item .facility-text::before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: rgba(255, 255, 255, .9);
|
||||
transition: .5s;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.facility-item .facility-text * {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.facility-item:hover .facility-icon::before,
|
||||
.facility-item:hover .facility-text::before {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.facility-item * {
|
||||
transition: .5s;
|
||||
}
|
||||
|
||||
.facility-item:hover * {
|
||||
color: #FFFFFF !important;
|
||||
}
|
||||
|
||||
/*** About ***/
|
||||
.about-img img {
|
||||
transition: .5s;
|
||||
}
|
||||
|
||||
.about-img img:hover {
|
||||
background: var(--primary) !important;
|
||||
}
|
||||
|
||||
/*** Classes ***/
|
||||
.classes-item {
|
||||
transition: .5s;
|
||||
}
|
||||
|
||||
.classes-item:hover {
|
||||
margin-top: -10px;
|
||||
}
|
||||
|
||||
/*** Team ***/
|
||||
.team-item .team-text {
|
||||
position: absolute;
|
||||
width: 250px;
|
||||
height: 250px;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
background: #FFFFFF;
|
||||
border: 17px solid var(--light);
|
||||
border-radius: 250px;
|
||||
transition: .5s;
|
||||
}
|
||||
|
||||
.team-item:hover .team-text {
|
||||
border-color: var(--primary);
|
||||
}
|
||||
|
||||
/*** Forms ***/
|
||||
.form-check {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.form-check-input {
|
||||
appearance: none;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 4px;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.form-check-input:checked {
|
||||
background-color: #008000;
|
||||
}
|
||||
|
||||
.form-check-input:checked::before {
|
||||
font-size: 16px;
|
||||
color: white;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 4px;
|
||||
}
|
||||
|
||||
.form-check-label {
|
||||
color: #008000;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.form-check a {
|
||||
color: #008000;
|
||||
text-decoration: none;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.form-check a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.is-invalid {
|
||||
border-color: red !important;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: red;
|
||||
font-size: 0.9em;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
/*** Testimonial ***/
|
||||
.testimonial-carousel {
|
||||
padding-left: 1.5rem;
|
||||
padding-right: 1.5rem;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.testimonial-carousel {
|
||||
padding-left: 4rem;
|
||||
padding-right: 4rem;
|
||||
}
|
||||
}
|
||||
|
||||
.testimonial-carousel .testimonial-item .border {
|
||||
border: 1px dashed rgba(0, 185, 142, .3) !important;
|
||||
}
|
||||
|
||||
.testimonial-carousel .owl-nav {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 45px;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
transform: translateY(-50%);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.testimonial-carousel .owl-nav .owl-prev,
|
||||
.testimonial-carousel .owl-nav .owl-next {
|
||||
position: relative;
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #FFFFFF;
|
||||
background: var(--primary);
|
||||
border-radius: 45px;
|
||||
font-size: 20px;
|
||||
transition: .5s;
|
||||
}
|
||||
|
||||
.testimonial-carousel .owl-nav .owl-prev:hover,
|
||||
.testimonial-carousel .owl-nav .owl-next:hover {
|
||||
background: var(--dark);
|
||||
}
|
||||
|
||||
/*** Footer ***/
|
||||
.footer .btn.btn-social {
|
||||
margin-right: 5px;
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #FFFFFF;
|
||||
border: 1px solid rgba(255, 255, 255, 0.5);
|
||||
border-radius: 45px;
|
||||
transition: .3s;
|
||||
}
|
||||
|
||||
.footer .btn.btn-social:hover {
|
||||
border-color: var(--primary);
|
||||
background: var(--primary);
|
||||
}
|
||||
|
||||
.footer .btn.btn-link {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
font-size: 16px;
|
||||
font-weight: normal;
|
||||
text-transform: capitalize;
|
||||
transition: .3s;
|
||||
}
|
||||
|
||||
.footer .btn.btn-link::before {
|
||||
position: relative;
|
||||
content: "\f105";
|
||||
font-family: "Font Awesome 5 Free";
|
||||
font-weight: 900;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.footer .btn.btn-link:hover {
|
||||
color: var(--primary) !important;
|
||||
letter-spacing: 1px;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.footer .form-control {
|
||||
border-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.footer .copyright {
|
||||
padding: 25px 0;
|
||||
font-size: 15px;
|
||||
border-top: 1px solid rgba(256, 256, 256, .1);
|
||||
}
|
||||
|
||||
.footer .copyright a {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.footer .footer-menu a {
|
||||
margin-right: 15px;
|
||||
padding-right: 15px;
|
||||
border-right: 1px solid rgba(255, 255, 255, .1);
|
||||
}
|
||||
|
||||
.footer .copyright a:hover,
|
||||
.footer .footer-menu a:hover {
|
||||
color: var(--primary) !important;
|
||||
}
|
||||
|
||||
.footer .footer-menu a:last-child {
|
||||
margin-right: 0;
|
||||
padding-right: 0;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
/*** User Registration ***/
|
||||
.user-registration {
|
||||
color: #FFFFFF;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.user-registration:hover {
|
||||
color: #008000;
|
||||
}
|
||||
|
||||
/*** Custom Colors ***/
|
||||
.text-purple {
|
||||
color: #6f42c1 !important;
|
||||
}
|
||||
|
||||
.text-teal {
|
||||
color: #20c997 !important;
|
||||
}
|
||||
|
||||
.text-orange {
|
||||
color: #fd7e14 !important;
|
||||
}
|
||||
|
||||
.text-pink {
|
||||
color: #e83e8c !important;
|
||||
}
|
||||
|
||||
.text-red {
|
||||
color: #f00e0e !important;
|
||||
}
|
||||
|
||||
.text-gray {
|
||||
color: #6c757d !important;
|
||||
}
|
||||
|
||||
.bg-purple {
|
||||
background-color: #6f42c1 !important;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.bg-teal {
|
||||
background-color: #20c997 !important;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.bg-orange {
|
||||
background-color: #fd7e14 !important;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.bg-pink {
|
||||
background-color: #e83e8c !important;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.bg-gray {
|
||||
background-color: #f8f9fa !important;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
/* Image Container Styles */
|
||||
.image-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 600px; /* Increased container width */
|
||||
height: 400px; /* Adjusted height for 4 images */
|
||||
margin: 30px auto;
|
||||
padding-bottom: 40px; /* Footer spacing */
|
||||
}
|
||||
|
||||
/* Base Image Styles */
|
||||
.overlapping-image {
|
||||
position: absolute;
|
||||
width: 250px;
|
||||
height: 250px;
|
||||
object-fit: cover;
|
||||
border: 5px solid white;
|
||||
box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
/* Individual Image Positioning */
|
||||
.image1 {
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 4;
|
||||
transform: rotate(-5deg);
|
||||
}
|
||||
|
||||
.image2 {
|
||||
top: 30px;
|
||||
left: 180px;
|
||||
z-index: 3;
|
||||
transform: rotate(17deg);
|
||||
}
|
||||
|
||||
.image3 {
|
||||
top: 120px;
|
||||
left: 30px;
|
||||
z-index: 2;
|
||||
transform: rotate(-15deg);
|
||||
}
|
||||
|
||||
.image4 {
|
||||
top: 150px;
|
||||
left: 200px;
|
||||
z-index: 1;
|
||||
transform: rotate(9deg);
|
||||
}
|
||||
|
||||
/* Hover Effects */
|
||||
.overlapping-image:hover {
|
||||
z-index: 10;
|
||||
transform: scale(1.05) rotate(0deg);
|
||||
box-shadow: 0 0 20px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
/* Tablet and Mobile Responsiveness */
|
||||
@media (max-width: 768px) {
|
||||
.image-container {
|
||||
height: 700px; /* Taller container for 4 images */
|
||||
margin-bottom: 80px;
|
||||
}
|
||||
|
||||
/* Adjusted positioning for tablet */
|
||||
.image1 {
|
||||
top: 40px;
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
.image2 {
|
||||
top: 40px;
|
||||
left: calc(50% - 100px);
|
||||
}
|
||||
|
||||
.image3 {
|
||||
top: 280px;
|
||||
left: 20px;
|
||||
}
|
||||
|
||||
.image4 {
|
||||
top: 280px;
|
||||
left: calc(50% - 90px);
|
||||
}
|
||||
|
||||
.overlapping-image {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile Responsiveness (smaller devices) */
|
||||
@media (max-width: 500px) {
|
||||
.image-container {
|
||||
height: 800px; /* Even taller for small screens */
|
||||
}
|
||||
|
||||
.overlapping-image {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
.image1 {
|
||||
top: 30px;
|
||||
left: 15px;
|
||||
}
|
||||
|
||||
.image2 {
|
||||
top: 30px;
|
||||
left: calc(50% - 75px);
|
||||
}
|
||||
|
||||
.image3 {
|
||||
top: 200px;
|
||||
left: 15px;
|
||||
}
|
||||
|
||||
.image4 {
|
||||
top: 200px;
|
||||
left: calc(50% - 75px);
|
||||
}
|
||||
}
|
||||
|
||||
/* Very small mobile devices */
|
||||
@media (max-width: 400px) {
|
||||
.image-container {
|
||||
height: 650px;
|
||||
}
|
||||
|
||||
.overlapping-image {
|
||||
width: 130px;
|
||||
height: 130px;
|
||||
}
|
||||
|
||||
.image2, .image4 {
|
||||
left: calc(50% - 65px);
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 340 KiB |
|
After Width: | Height: | Size: 476 KiB |
|
After Width: | Height: | Size: 282 KiB |
|
After Width: | Height: | Size: 452 KiB |
|
After Width: | Height: | Size: 362 KiB |
|
After Width: | Height: | Size: 540 KiB |
|
After Width: | Height: | Size: 112 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 364 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 229 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 4.9 MiB |
|
After Width: | Height: | Size: 2.3 MiB |
|
After Width: | Height: | Size: 5.1 MiB |
|
After Width: | Height: | Size: 4.2 MiB |
|
After Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 373 KiB |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 724 KiB |
|
After Width: | Height: | Size: 112 KiB |
|
After Width: | Height: | Size: 112 KiB |
|
After Width: | Height: | Size: 156 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 27 KiB |
@@ -0,0 +1,4 @@
|
||||
$(document).ready(function(){
|
||||
$('#birth-date').mask('00/00/0000');
|
||||
$('#phone-number').mask('0000-0000');
|
||||
})
|
||||
@@ -0,0 +1,73 @@
|
||||
export function validateAgeLive(inputEl, minAge = 5, maxAge = 18) {
|
||||
const dob = inputEl.value.trim();
|
||||
if (!dob) return false;
|
||||
|
||||
const birthDate = normalize(new Date(dob));
|
||||
if (isNaN(birthDate.getTime())) {
|
||||
showFeedback(inputEl, false, "Invalid date format (Use YYYY-MM-DD)");
|
||||
return false;
|
||||
}
|
||||
|
||||
const registrationAgeDeadline = window.appConfig?.registrationAgeDeadline;
|
||||
const deadline = normalize(new Date(registrationAgeDeadline || new Date()));
|
||||
|
||||
// Calculate boundaries - add one day to effectively make the age calculation more lenient
|
||||
const adjustedDeadline = new Date(deadline);
|
||||
adjustedDeadline.setDate(adjustedDeadline.getDate() + 1);
|
||||
|
||||
const minBirthDate = new Date(Date.UTC(adjustedDeadline.getFullYear() - maxAge, adjustedDeadline.getMonth(), adjustedDeadline.getDate())); // oldest
|
||||
const maxBirthDate = new Date(Date.UTC(adjustedDeadline.getFullYear() - minAge, adjustedDeadline.getMonth(), adjustedDeadline.getDate())); // youngest
|
||||
|
||||
const isValidAge = birthDate >= minBirthDate && birthDate <= maxBirthDate;
|
||||
|
||||
// Format date as MM-DD-YYYY for error message (use original deadline, not adjusted)
|
||||
const formatDateMMDDYYYY = (dateStr) => {
|
||||
if (!dateStr) return dateStr;
|
||||
// Parse as UTC to avoid timezone issues that could shift the date
|
||||
const date = new Date(dateStr + 'T00:00:00.000Z');
|
||||
if (isNaN(date.getTime())) return dateStr;
|
||||
const month = String(date.getUTCMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getUTCDate()).padStart(2, '0');
|
||||
const year = date.getUTCFullYear();
|
||||
return `${month}-${day}-${year}`;
|
||||
};
|
||||
|
||||
const errorMessage = isValidAge
|
||||
? ""
|
||||
: `Must be ${minAge}-${maxAge} years old by ${formatDateMMDDYYYY(registrationAgeDeadline)}.`;
|
||||
|
||||
showFeedback(inputEl, isValidAge, errorMessage);
|
||||
return isValidAge;
|
||||
}
|
||||
|
||||
function normalize(date) {
|
||||
return new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
|
||||
}
|
||||
|
||||
|
||||
// Helper function (unchanged)
|
||||
function showFeedback(inputEl, isValid, message) {
|
||||
inputEl.classList.toggle("is-invalid", !isValid);
|
||||
const feedbackEl = inputEl.nextElementSibling || document.createElement("div");
|
||||
feedbackEl.textContent = message;
|
||||
feedbackEl.className = `invalid-feedback ${isValid ? "d-none" : ""}`;
|
||||
inputEl.parentNode.appendChild(feedbackEl);
|
||||
}
|
||||
|
||||
// Set max birthdate (2025 - minAge)
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
const minAge = 5; // Must match validateAgeLive's default
|
||||
const deadline = window.appConfig?.registrationAgeDeadline
|
||||
? new Date(window.appConfig.registrationAgeDeadline)
|
||||
: new Date();
|
||||
const maxBirthYear = deadline.getFullYear() - minAge;
|
||||
const maxDateStr = `${maxBirthYear}-12-31`; // Latest allowed birthdate
|
||||
|
||||
document.querySelectorAll(".dob-input").forEach((input) => {
|
||||
input.setAttribute("max", maxDateStr);
|
||||
input.setAttribute(
|
||||
"aria-label",
|
||||
`Born before ${maxBirthYear + 1} (${minAge}+ years by ${deadline.getFullYear()})`
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
// email_validation.js
|
||||
export function validateEmailLive(inputEl) {
|
||||
const emailRE = /^(?=.{2,50}$)[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/;
|
||||
const val = inputEl.value.trim();
|
||||
const valid = val === '' || emailRE.test(val);
|
||||
const message = valid ? '' : "Email must be in the format: user@example.com.";
|
||||
|
||||
inputEl.classList.toggle('is-invalid', !valid);
|
||||
|
||||
let feedback = inputEl.nextElementSibling;
|
||||
if (!feedback || !feedback.classList.contains('invalid-feedback')) {
|
||||
feedback = document.createElement('div');
|
||||
feedback.className = 'invalid-feedback';
|
||||
inputEl.parentNode.insertBefore(feedback, inputEl.nextSibling);
|
||||
}
|
||||
|
||||
feedback.textContent = message;
|
||||
feedback.style.display = valid ? 'none' : 'block';
|
||||
|
||||
return valid;
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/* modal_validation.js
|
||||
live client-side checks for each .student-edit-form
|
||||
─────────────────────────────────────────────── */
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
/* basic regex rules */
|
||||
const nameRE = /^[A-Za-z\s-]{2,30}$/;
|
||||
const gradeRE = /^[A-Za-z0-9\s-]{1,20}$/;
|
||||
const MIN_AGE = 5, MAX_AGE = 18;
|
||||
|
||||
const calcAge = (d) => {
|
||||
const dob = new Date(d);
|
||||
const today = new Date();
|
||||
let a = today.getFullYear() - dob.getFullYear();
|
||||
const m = today.getMonth() - dob.getMonth();
|
||||
if (m < 0 || (m === 0 && today.getDate() < dob.getDate())) a--;
|
||||
return a;
|
||||
};
|
||||
|
||||
/* attach to every modal edit-form */
|
||||
document.querySelectorAll('.student-edit-form').forEach(form => {
|
||||
|
||||
const feedback = (el, msg) => {
|
||||
let f = el.nextElementSibling;
|
||||
if (!f || !f.classList.contains('invalid-feedback')) {
|
||||
f = document.createElement('div');
|
||||
f.className = 'invalid-feedback';
|
||||
el.after(f);
|
||||
}
|
||||
f.textContent = msg;
|
||||
el.classList.add('is-invalid');
|
||||
};
|
||||
|
||||
const clear = el => {
|
||||
el.classList.remove('is-invalid');
|
||||
const f = el.nextElementSibling;
|
||||
if (f && f.classList.contains('invalid-feedback')) f.textContent = '';
|
||||
};
|
||||
|
||||
/* live input validation */
|
||||
form.addEventListener('input', e => {
|
||||
const t = e.target;
|
||||
clear(t); // reset first
|
||||
|
||||
if (t.name === 'firstname' || t.name === 'lastname') {
|
||||
if (!nameRE.test(t.value.trim())) {
|
||||
feedback(t, 'Only letters, spaces or dashes (2-30)');
|
||||
}
|
||||
}
|
||||
|
||||
if (t.name === 'registration_grade') {
|
||||
if (!gradeRE.test(t.value.trim())) {
|
||||
feedback(t, 'Invalid grade value');
|
||||
}
|
||||
}
|
||||
|
||||
if (t.name === 'dob') {
|
||||
const age = calcAge(t.value);
|
||||
if (!t.value || age < MIN_AGE || age > MAX_AGE) {
|
||||
feedback(t, `Age must be ${MIN_AGE}-${MAX_AGE}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/* submit validation */
|
||||
form.addEventListener('submit', e => {
|
||||
let firstBad = null;
|
||||
|
||||
form.querySelectorAll('input,select').forEach(el => clear(el)); // reset all
|
||||
|
||||
const fname = form.querySelector('[name="firstname"]');
|
||||
const lname = form.querySelector('[name="lastname"]');
|
||||
const dob = form.querySelector('[name="dob"]');
|
||||
const grade = form.querySelector('[name="registration_grade"]');
|
||||
const gender= form.querySelector('[name="gender"]');
|
||||
const photo = form.querySelector('[name="photo_consent"]');
|
||||
|
||||
/* run checks */
|
||||
if (!nameRE.test(fname.value.trim())) {
|
||||
feedback(fname,'Only letters, spaces or dashes (2-30)'); firstBad ??= fname;
|
||||
}
|
||||
if (!nameRE.test(lname.value.trim())) {
|
||||
feedback(lname,'Only letters, spaces or dashes (2-30)'); firstBad ??= lname;
|
||||
}
|
||||
const age = calcAge(dob.value);
|
||||
if (!dob.value || age < MIN_AGE || age > MAX_AGE) {
|
||||
feedback(dob,`Age must be ${MIN_AGE}-${MAX_AGE}`); firstBad ??= dob;
|
||||
}
|
||||
if (!gender.value) {
|
||||
feedback(gender,'Required'); firstBad ??= gender;
|
||||
}
|
||||
if (!photo.value) {
|
||||
feedback(photo,'Required'); firstBad ??= photo;
|
||||
}
|
||||
if (!gradeRE.test(grade.value.trim())) {
|
||||
feedback(grade,'Invalid grade'); firstBad ??= grade;
|
||||
}
|
||||
|
||||
if (firstBad) {
|
||||
e.preventDefault();
|
||||
firstBad.focus();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
console.log('modal_validation.js initialised ✅');
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
// name_validation.js
|
||||
export function validateNameLive(inputEl) {
|
||||
// Allow only letters, spaces, and dashes, 2–30 chars
|
||||
const nameRE = /^[A-Za-z -]{2,30}$/;
|
||||
const val = inputEl.value.trim();
|
||||
|
||||
const valid = nameRE.test(val);
|
||||
const message = valid ? '' : 'Only letters, spaces, or dashes (2–30 chars).';
|
||||
|
||||
// Toggle invalid class
|
||||
inputEl.classList.toggle('is-invalid', !valid);
|
||||
|
||||
// Find or create feedback element
|
||||
let feedback = inputEl.nextElementSibling;
|
||||
while (feedback && feedback.nodeType === Node.TEXT_NODE) {
|
||||
feedback = feedback.nextSibling;
|
||||
}
|
||||
if (!feedback || !feedback.classList.contains('invalid-feedback')) {
|
||||
feedback = document.createElement('div');
|
||||
feedback.className = 'invalid-feedback';
|
||||
inputEl.parentNode.insertBefore(feedback, inputEl.nextSibling);
|
||||
}
|
||||
|
||||
feedback.textContent = message;
|
||||
feedback.style.display = valid ? 'none' : 'block';
|
||||
|
||||
// Sync with browser constraint validation
|
||||
inputEl.setCustomValidity(valid ? '' : message);
|
||||
|
||||
return valid;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// Phone validation and formatting function
|
||||
export function validatePhoneLive(inputEl) {
|
||||
// Get current value and remove any non-digit characters
|
||||
let val = inputEl.value.replace(/\D/g, '');
|
||||
|
||||
// Format the phone number as 123-456-7890
|
||||
let formattedValue = '';
|
||||
if (val.length > 0) {
|
||||
formattedValue = val.substring(0, 3);
|
||||
if (val.length > 3) {
|
||||
formattedValue += '-' + val.substring(3, 6);
|
||||
}
|
||||
if (val.length > 6) {
|
||||
formattedValue += '-' + val.substring(6, 10);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the input value with formatted version
|
||||
inputEl.value = formattedValue;
|
||||
|
||||
// Check if the phone number is valid (exactly 10 digits)
|
||||
const valid = val.length === 10;
|
||||
|
||||
// Update validation UI - add red border for invalid input
|
||||
if (val.length === 0) {
|
||||
inputEl.classList.remove('is-invalid', 'is-valid');
|
||||
} else if (valid) {
|
||||
inputEl.classList.remove('is-invalid');
|
||||
inputEl.classList.add('is-valid');
|
||||
} else {
|
||||
inputEl.classList.remove('is-valid');
|
||||
inputEl.classList.add('is-invalid');
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
// Prevent form submission if phone is invalid
|
||||
function preventInvalidSubmission() {
|
||||
const forms = document.querySelectorAll('form');
|
||||
forms.forEach(form => {
|
||||
form.addEventListener('submit', function (e) {
|
||||
const phoneInput = document.getElementById('phoneInput');
|
||||
if (phoneInput && !validatePhoneLive(phoneInput)) {
|
||||
e.preventDefault();
|
||||
phoneInput.focus();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize the event listener when the page loads
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const phoneInput = document.getElementById('phoneInput');
|
||||
|
||||
// Add input event listener for live validation and formatting
|
||||
phoneInput.addEventListener('input', function () {
|
||||
validatePhoneLive(this);
|
||||
});
|
||||
|
||||
// Add blur event listener to finalize formatting
|
||||
phoneInput.addEventListener('blur', function () {
|
||||
validatePhoneLive(this);
|
||||
});
|
||||
|
||||
// Add focus event to select all text when focused
|
||||
phoneInput.addEventListener('focus', function () {
|
||||
this.select();
|
||||
});
|
||||
|
||||
// Prevent form submission for invalid phone numbers
|
||||
preventInvalidSubmission();
|
||||
});
|
||||
@@ -0,0 +1,189 @@
|
||||
(() => {
|
||||
const endpoint = window.PROOFREAD_ENDPOINT;
|
||||
if (!endpoint) return;
|
||||
|
||||
const csrfNameEl = document.getElementById('proofreadCsrfName');
|
||||
const csrfHashEl = document.getElementById('proofreadCsrfHash');
|
||||
|
||||
const resize = (el) => {
|
||||
if (!el) return;
|
||||
el.style.height = 'auto';
|
||||
el.style.height = `${el.scrollHeight}px`;
|
||||
};
|
||||
|
||||
const isSafeMatch = (m) => {
|
||||
const issueType = (m.rule?.issueType || '').toLowerCase();
|
||||
const repl = m.replacements || [];
|
||||
if (repl.length !== 1) return false;
|
||||
|
||||
const len = m.length ?? 0;
|
||||
if (len > 25) return false;
|
||||
|
||||
if (issueType === 'misspelling') return true;
|
||||
|
||||
const msg = (m.message || '').toLowerCase();
|
||||
if (msg.includes('whitespace') || msg.includes('space') || msg.includes('punctuation')) return true;
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const applyMatches = (text, matches) => {
|
||||
const sorted = [...matches].sort((a, b) => (b.offset || 0) - (a.offset || 0));
|
||||
let out = text;
|
||||
|
||||
for (const m of sorted) {
|
||||
const offset = m.offset || 0;
|
||||
const length = m.length || 0;
|
||||
const repl = m.replacements?.[0]?.value;
|
||||
if (typeof repl !== 'string') continue;
|
||||
|
||||
out = out.slice(0, offset) + repl + out.slice(offset + length);
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
const refreshCsrf = (hash) => {
|
||||
if (!hash) return;
|
||||
if (csrfHashEl) csrfHashEl.value = hash;
|
||||
const name = csrfNameEl ? csrfNameEl.value : null;
|
||||
if (!name) return;
|
||||
document.querySelectorAll(`input[name="${name}"]`).forEach((el) => {
|
||||
el.value = hash;
|
||||
});
|
||||
};
|
||||
|
||||
const setStatus = (targetId, message, isError = false) => {
|
||||
const status = document.querySelector(`[data-status-for="${targetId}"]`);
|
||||
if (!status) return;
|
||||
status.textContent = message;
|
||||
status.classList.toggle('text-danger', !!isError);
|
||||
};
|
||||
|
||||
const clearList = (targetId) => {
|
||||
const list = document.querySelector(`[data-list-for="${targetId}"]`);
|
||||
if (list) list.innerHTML = '';
|
||||
};
|
||||
|
||||
const renderRisky = (targetId, risky, textarea) => {
|
||||
const list = document.querySelector(`[data-list-for="${targetId}"]`);
|
||||
if (!list) return;
|
||||
list.innerHTML = '';
|
||||
|
||||
risky.forEach((match) => {
|
||||
const li = document.createElement('li');
|
||||
li.className = 'mb-1';
|
||||
|
||||
const suggestion = match.replacements?.[0]?.value || '(no suggestion)';
|
||||
const label = document.createElement('span');
|
||||
label.textContent = `${match.message || 'Suggestion'} → ${suggestion}`;
|
||||
|
||||
const btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = 'btn btn-link btn-sm ps-0';
|
||||
btn.textContent = 'Apply';
|
||||
btn.addEventListener('click', () => {
|
||||
textarea.value = applyMatches(textarea.value, [match]);
|
||||
resize(textarea);
|
||||
textarea.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
li.remove();
|
||||
});
|
||||
|
||||
li.appendChild(label);
|
||||
li.appendChild(document.createTextNode(' '));
|
||||
li.appendChild(btn);
|
||||
list.appendChild(li);
|
||||
});
|
||||
};
|
||||
|
||||
const proofreadField = async (textarea) => {
|
||||
if (!textarea) return { ok: false };
|
||||
const targetId = textarea.id || textarea.name || '';
|
||||
|
||||
const text = textarea.value || '';
|
||||
if (!text.trim()) {
|
||||
setStatus(targetId, 'Enter text to proofread.', true);
|
||||
clearList(targetId);
|
||||
return { ok: false };
|
||||
}
|
||||
|
||||
setStatus(targetId, 'Checking...');
|
||||
clearList(targetId);
|
||||
|
||||
const body = new URLSearchParams();
|
||||
body.append('text', text);
|
||||
const name = csrfNameEl ? csrfNameEl.value : null;
|
||||
const hash = csrfHashEl ? csrfHashEl.value : null;
|
||||
if (name && hash) body.append(name, hash);
|
||||
|
||||
let data;
|
||||
try {
|
||||
const resp = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body,
|
||||
});
|
||||
data = await resp.json();
|
||||
} catch (err) {
|
||||
setStatus(targetId, 'Network error.', true);
|
||||
return { ok: false, error: 'Network error.' };
|
||||
}
|
||||
|
||||
refreshCsrf(data?.csrfHash);
|
||||
|
||||
if (!data || !data.ok) {
|
||||
setStatus(targetId, data?.error || 'Proofread failed.', true);
|
||||
return { ok: false, error: data?.error || 'Proofread failed.' };
|
||||
}
|
||||
|
||||
const matches = data.result?.matches || [];
|
||||
if (!matches.length) {
|
||||
setStatus(targetId, 'No issues found.');
|
||||
return { ok: true, safe: 0, risky: 0 };
|
||||
}
|
||||
|
||||
const safe = matches.filter(isSafeMatch);
|
||||
const risky = matches.filter((m) => !isSafeMatch(m));
|
||||
|
||||
const updated = applyMatches(text, safe);
|
||||
if (updated !== text) {
|
||||
textarea.value = updated;
|
||||
resize(textarea);
|
||||
textarea.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
}
|
||||
|
||||
renderRisky(targetId, risky, textarea);
|
||||
setStatus(targetId, `Auto-fixed: ${safe.length}. Needs review: ${risky.length}.`);
|
||||
return { ok: true, safe: safe.length, risky: risky.length };
|
||||
};
|
||||
|
||||
const proofreadAll = async () => {
|
||||
const btn = document.getElementById('proofreadAllBtn');
|
||||
const summary = document.getElementById('proofreadAllStatus');
|
||||
const fields = Array.from(document.querySelectorAll('textarea.review-field'));
|
||||
if (!fields.length) return;
|
||||
|
||||
if (btn) btn.disabled = true;
|
||||
if (summary) summary.textContent = 'Checking all reviews...';
|
||||
|
||||
let totalSafe = 0;
|
||||
let totalRisky = 0;
|
||||
let errors = 0;
|
||||
|
||||
for (const field of fields) {
|
||||
const result = await proofreadField(field);
|
||||
if (result?.safe) totalSafe += result.safe;
|
||||
if (result?.risky) totalRisky += result.risky;
|
||||
if (!result?.ok) errors += 1;
|
||||
}
|
||||
|
||||
if (summary) {
|
||||
summary.textContent = `Auto-fixed: ${totalSafe}. Needs review: ${totalRisky}. ${errors ? 'Errors on ' + errors + ' field(s).' : ''}`;
|
||||
}
|
||||
if (btn) btn.disabled = false;
|
||||
};
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const btn = document.getElementById('proofreadAllBtn');
|
||||
if (btn) btn.addEventListener('click', proofreadAll);
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,118 @@
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const form = document.querySelector("form");
|
||||
const email = document.getElementById("email");
|
||||
const confirmEmail = document.getElementById("confirmEmail");
|
||||
|
||||
// Disable copy/paste for confirm email
|
||||
confirmEmail.addEventListener("paste", function (e) {
|
||||
e.preventDefault();
|
||||
alert("Pasting is disabled in the confirm email field.");
|
||||
});
|
||||
confirmEmail.addEventListener("copy", function (e) {
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
form.addEventListener("submit", function (e) {
|
||||
let isValid = true;
|
||||
let messages = [];
|
||||
|
||||
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
const alphanumericPattern = /^[A-Za-z0-9 ]+$/;
|
||||
const alphaOnlyPattern = /^[A-Za-z ]+$/;
|
||||
|
||||
// Validate required fields
|
||||
const requiredFields = [
|
||||
"firstName", "lastName", "email", "confirmEmail", "phone",
|
||||
"street", "city", "state", "zip", "captcha", "acceptPolicy"
|
||||
];
|
||||
|
||||
for (const fieldId of requiredFields) {
|
||||
const field = document.getElementById(fieldId);
|
||||
if (field) {
|
||||
if ((field.type === "checkbox" || field.type === "radio") && !field.checked) {
|
||||
const groupName = field.name || field.id;
|
||||
const anyChecked = document.querySelector(`input[name='${groupName}']:checked`);
|
||||
if (!anyChecked) {
|
||||
messages.push(`Please select a value for ${groupName}.`);
|
||||
isValid = false;
|
||||
}
|
||||
} else if (!field.value.trim()) {
|
||||
messages.push(`Please fill out the ${fieldId} field.`);
|
||||
isValid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Gender radio group validation
|
||||
const genderSelected = document.querySelector("input[name='gender']:checked");
|
||||
if (!genderSelected) {
|
||||
messages.push("Please select your gender.");
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
// Email format check
|
||||
if (!emailPattern.test(email.value.trim())) {
|
||||
messages.push("Email format is invalid.");
|
||||
isValid = false;
|
||||
}
|
||||
if (!emailPattern.test(confirmEmail.value.trim())) {
|
||||
messages.push("Confirm Email format is invalid.");
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
// Email match check
|
||||
if (email.value.trim() !== confirmEmail.value.trim()) {
|
||||
messages.push("Email and Confirm Email do not match.");
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
// ZIP code validation
|
||||
const zip = document.getElementById("zip");
|
||||
if (zip && !/^\d{5}$/.test(zip.value.trim())) {
|
||||
messages.push("ZIP code must be exactly 5 digits.");
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
// Alphanumeric field validation
|
||||
const alphanumericFields = ["firstName", "lastName", "street", "apt"];
|
||||
for (const id of alphanumericFields) {
|
||||
const field = document.getElementById(id);
|
||||
if (field && field.value.trim() !== "" && !alphanumericPattern.test(field.value)) {
|
||||
messages.push(`${id} must contain only letters, numbers, and spaces.`);
|
||||
isValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
// City must contain only letters and spaces
|
||||
const city = document.getElementById("city");
|
||||
if (city && city.value.trim() !== "" && !alphaOnlyPattern.test(city.value)) {
|
||||
messages.push("City must contain only letters and spaces.");
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
// Character length validation
|
||||
const lengthChecks = [
|
||||
{ id: "firstName", max: 30 },
|
||||
{ id: "lastName", max: 30 },
|
||||
{ id: "email", max: 30 },
|
||||
{ id: "confirmEmail", max: 30 },
|
||||
{ id: "street", max: 30 },
|
||||
{ id: "city", max: 30 },
|
||||
{ id: "phone", max: 14 },
|
||||
{ id: "apt", max: 5 },
|
||||
];
|
||||
for (const check of lengthChecks) {
|
||||
const field = document.getElementById(check.id);
|
||||
if (field && field.value.length > check.max) {
|
||||
messages.push(`${check.id} must not exceed ${check.max} characters.`);
|
||||
isValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isValid) {
|
||||
e.preventDefault();
|
||||
alert(messages.join("\n"));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const studentSelect = document.getElementById('student_id');
|
||||
const classSelect = document.getElementById('class_section_id');
|
||||
|
||||
const studentFrame = document.getElementById('studentReportFrame');
|
||||
const classFrame = document.getElementById('classReportFrame');
|
||||
|
||||
const downloadStudentBtn = document.getElementById('downloadStudentBtn');
|
||||
const downloadClassBtn = document.getElementById('downloadClassBtn');
|
||||
|
||||
const printStudentBtn = document.getElementById('printStudentBtn');
|
||||
const printClassBtn = document.getElementById('printClassBtn');
|
||||
|
||||
document.getElementById('studentForm').addEventListener('submit', function (e) {
|
||||
e.preventDefault();
|
||||
const studentId = studentSelect.value;
|
||||
const viewUrl = `/report-card/student/${studentId}`;
|
||||
studentFrame.src = viewUrl;
|
||||
studentFrame.hidden = false;
|
||||
downloadStudentBtn.href = viewUrl + "?download=1";
|
||||
});
|
||||
|
||||
document.getElementById('classForm').addEventListener('submit', function (e) {
|
||||
e.preventDefault();
|
||||
const classId = classSelect.value;
|
||||
const viewUrl = `/report-card/class/${classId}`;
|
||||
classFrame.src = viewUrl;
|
||||
classFrame.hidden = false;
|
||||
downloadClassBtn.href = viewUrl + "?download=1";
|
||||
});
|
||||
|
||||
printStudentBtn.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
printIframe('studentReportFrame');
|
||||
});
|
||||
|
||||
printClassBtn.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
printIframe('classReportFrame');
|
||||
});
|
||||
|
||||
function printIframe(id) {
|
||||
const frame = document.getElementById(id);
|
||||
frame.contentWindow.focus();
|
||||
frame.contentWindow.print();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,247 @@
|
||||
class SessionTimeoutManager {
|
||||
constructor() {
|
||||
this.config = {
|
||||
timeout: 1800, // default 30 min
|
||||
warning_time: 1500, // default 5 min before timeout
|
||||
check_interval: 60000, // 1 minute
|
||||
logout_url: '/auth/logout',
|
||||
keep_alive_url: '/session/ping-activity',
|
||||
check_url: '/session/check-timeout'
|
||||
};
|
||||
this.timers = {
|
||||
checkTimer: null,
|
||||
logoutTimer: null,
|
||||
warningTimer: null
|
||||
};
|
||||
this.modal = null;
|
||||
this.warningShown = false;
|
||||
}
|
||||
|
||||
async init() {
|
||||
try {
|
||||
await this.fetchConfig();
|
||||
this.setupEventListeners();
|
||||
this.startPeriodicChecks();
|
||||
console.log('Session timeout manager initialized with config:', this.config);
|
||||
} catch (error) {
|
||||
console.warn('Session timeout using default config due to error:', error.message);
|
||||
this.setupEventListeners();
|
||||
this.startPeriodicChecks();
|
||||
}
|
||||
}
|
||||
|
||||
async fetchConfig() {
|
||||
try {
|
||||
const response = await fetch('/session/get-timeout-config', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
},
|
||||
credentials: 'same-origin'
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
this.config = { ...this.config, ...data };
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to load timeout config, using defaults:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
// Reset timers on user activity
|
||||
const events = ['mousedown', 'keydown', 'scroll', 'touchstart', 'click', 'input'];
|
||||
events.forEach(event => {
|
||||
document.addEventListener(event, () => this.resetActivity(), { passive: true });
|
||||
});
|
||||
|
||||
// Handle visibility change
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (!document.hidden) {
|
||||
this.checkSessionStatus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async resetActivity() {
|
||||
try {
|
||||
await fetch(this.config.keep_alive_url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
},
|
||||
credentials: 'same-origin'
|
||||
});
|
||||
this.clearWarning();
|
||||
} catch (error) {
|
||||
console.warn('Activity reset failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
startPeriodicChecks() {
|
||||
this.timers.checkTimer = setInterval(() => {
|
||||
this.checkSessionStatus();
|
||||
}, this.config.check_interval);
|
||||
}
|
||||
|
||||
async checkSessionStatus() {
|
||||
try {
|
||||
const response = await fetch(this.config.check_url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
},
|
||||
credentials: 'same-origin'
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
switch (data.status) {
|
||||
case 'expired':
|
||||
this.handleSessionExpired(data);
|
||||
break;
|
||||
case 'warning':
|
||||
this.handleSessionWarning(data);
|
||||
break;
|
||||
case 'active':
|
||||
this.handleSessionActive(data);
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Session status check failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
handleSessionExpired(data) {
|
||||
this.clearWarning();
|
||||
clearInterval(this.timers.checkTimer);
|
||||
|
||||
if (data.redirect) {
|
||||
// Show message before redirect
|
||||
alert(data.message || 'Your session has expired. Please log in again.');
|
||||
window.location.href = data.redirect;
|
||||
} else {
|
||||
window.location.href = this.config.logout_url;
|
||||
}
|
||||
}
|
||||
|
||||
handleSessionWarning(data) {
|
||||
if (!this.warningShown) {
|
||||
this.showWarning(data.time_remaining);
|
||||
} else {
|
||||
this.updateWarning(data.time_remaining);
|
||||
}
|
||||
}
|
||||
|
||||
handleSessionActive(data) {
|
||||
this.clearWarning();
|
||||
}
|
||||
|
||||
showWarning(timeRemaining) {
|
||||
this.warningShown = true;
|
||||
this.createWarningModal(timeRemaining);
|
||||
}
|
||||
|
||||
updateWarning(timeRemaining) {
|
||||
if (this.modal) {
|
||||
const countdownElement = this.modal.querySelector('.countdown');
|
||||
if (countdownElement) {
|
||||
countdownElement.textContent = Math.max(0, timeRemaining);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
createWarningModal(timeRemaining) {
|
||||
this.hideWarning();
|
||||
|
||||
this.modal = document.createElement('div');
|
||||
this.modal.className = 'session-timeout-modal';
|
||||
this.modal.style.cssText = `
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0,0,0,0.8);
|
||||
z-index: 99999;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-family: Arial, sans-serif;
|
||||
`;
|
||||
|
||||
this.modal.innerHTML = `
|
||||
<div style="background: white; padding: 30px; border-radius: 10px; text-align: center; max-width: 400px; box-shadow: 0 5px 25px rgba(0,0,0,0.3);">
|
||||
<h3 style="color: #d35400; margin-bottom: 20px; font-size: 1.5em;">Session About to Expire</h3>
|
||||
<p style="font-size: 1.1em; margin-bottom: 20px;">
|
||||
Your session will expire in <span class="countdown" style="font-weight: bold; color: #e74c3c;">${timeRemaining}</span> seconds due to inactivity.
|
||||
</p>
|
||||
<div style="margin-top: 25px;">
|
||||
<button onclick="sessionTimeout.continueSession()"
|
||||
style="background: #27ae60; color: white; border: none; padding: 12px 24px;
|
||||
border-radius: 5px; cursor: pointer; margin-right: 15px; font-size: 1em;">
|
||||
Continue Session
|
||||
</button>
|
||||
<button onclick="sessionTimeout.logout()"
|
||||
style="background: #e74c3c; color: white; border: none; padding: 12px 24px;
|
||||
border-radius: 5px; cursor: pointer; font-size: 1em;">
|
||||
Logout Now
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(this.modal);
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
hideWarning() {
|
||||
if (this.modal) {
|
||||
this.modal.remove();
|
||||
this.modal = null;
|
||||
}
|
||||
this.warningShown = false;
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
clearWarning() {
|
||||
this.hideWarning();
|
||||
}
|
||||
|
||||
continueSession() {
|
||||
this.resetActivity();
|
||||
this.hideWarning();
|
||||
}
|
||||
|
||||
logout() {
|
||||
window.location.href = this.config.logout_url;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
clearInterval(this.timers.checkTimer);
|
||||
this.hideWarning();
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize globally
|
||||
const sessionTimeout = new SessionTimeoutManager();
|
||||
|
||||
// Start when DOM is ready
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => sessionTimeout.init());
|
||||
} else {
|
||||
sessionTimeout.init();
|
||||
}
|
||||
|
||||
// Make available globally
|
||||
window.sessionTimeout = sessionTimeout;
|
||||
@@ -0,0 +1,119 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const form = document.getElementById('gradesForm');
|
||||
if (!form) {
|
||||
console.warn("⚠️ Form with ID 'gradesForm' not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
const isScoreInRange = function (value) {
|
||||
const num = Number(value);
|
||||
return Number.isFinite(num) && num >= 0 && num <= 100;
|
||||
};
|
||||
|
||||
const toggleCommentCheckbox = function (wrapper) {
|
||||
if (!wrapper) return;
|
||||
const textarea = wrapper.querySelector('textarea');
|
||||
const label = wrapper.querySelector('label.missing-check');
|
||||
if (!textarea || !label) return;
|
||||
const empty = textarea.value.trim() === '';
|
||||
label.classList.toggle('d-none', !empty);
|
||||
if (!empty) {
|
||||
const checkbox = label.querySelector('input[type="checkbox"]');
|
||||
if (checkbox) checkbox.checked = false;
|
||||
}
|
||||
};
|
||||
|
||||
form.querySelectorAll('.comment-check').forEach(function (wrapper) {
|
||||
const textarea = wrapper.querySelector('textarea');
|
||||
if (!textarea) return;
|
||||
textarea.addEventListener('input', function () {
|
||||
toggleCommentCheckbox(wrapper);
|
||||
});
|
||||
toggleCommentCheckbox(wrapper);
|
||||
});
|
||||
|
||||
form.addEventListener('submit', function (event) {
|
||||
const submitter = event.submitter;
|
||||
if (!submitter || submitter.id !== 'submitScoresLockBtn') {
|
||||
return;
|
||||
}
|
||||
|
||||
const missingEntries = [];
|
||||
const invalidEntries = [];
|
||||
|
||||
form.querySelectorAll('#myTable tbody tr').forEach(function (row) {
|
||||
let studentName = row.getAttribute('data-student-name') || '';
|
||||
if (!studentName) {
|
||||
const cells = row.querySelectorAll('td');
|
||||
const first = cells[0] ? cells[0].textContent.trim() : '';
|
||||
const last = cells[1] ? cells[1].textContent.trim() : '';
|
||||
studentName = (first || last) ? (first + ' ' + last).trim() : 'Student';
|
||||
}
|
||||
const rowMissingSet = new Set();
|
||||
const rowInvalid = [];
|
||||
|
||||
const rawMissingDetails = row.getAttribute('data-missing-details') || '[]';
|
||||
let missingDetails = [];
|
||||
try {
|
||||
missingDetails = JSON.parse(rawMissingDetails) || [];
|
||||
} catch (_) {
|
||||
missingDetails = [];
|
||||
}
|
||||
|
||||
const commentLabels = new Set(['PTAP Comment', 'Midterm Comment', 'Final Comment']);
|
||||
missingDetails.forEach(function (label) {
|
||||
if (!commentLabels.has(label)) {
|
||||
rowMissingSet.add(label);
|
||||
}
|
||||
});
|
||||
|
||||
row.querySelectorAll('[data-score-check="1"]').forEach(function (cell) {
|
||||
const label = cell.getAttribute('data-field-label') || 'Score';
|
||||
const value = cell.getAttribute('data-field-value') || '';
|
||||
if (value !== '' && value !== null && !isScoreInRange(value)) {
|
||||
rowInvalid.push(label);
|
||||
}
|
||||
});
|
||||
|
||||
row.querySelectorAll('.comment-check').forEach(function (wrapper) {
|
||||
const label = wrapper.getAttribute('data-field-label') || 'Comment';
|
||||
const textarea = wrapper.querySelector('textarea');
|
||||
const checkbox = wrapper.querySelector('input.missing-comment-checkbox');
|
||||
const value = textarea ? textarea.value.trim() : '';
|
||||
if (value === '' && (!checkbox || !checkbox.checked)) {
|
||||
rowMissingSet.add(label);
|
||||
}
|
||||
});
|
||||
|
||||
const rowMissing = Array.from(rowMissingSet);
|
||||
if (rowMissing.length) {
|
||||
missingEntries.push(studentName + ': ' + rowMissing.join(', '));
|
||||
}
|
||||
if (rowInvalid.length) {
|
||||
invalidEntries.push(studentName + ': ' + rowInvalid.join(', '));
|
||||
}
|
||||
});
|
||||
|
||||
if (invalidEntries.length || missingEntries.length) {
|
||||
event.preventDefault();
|
||||
const lines = [];
|
||||
if (invalidEntries.length) {
|
||||
lines.push('Scores must be between 0 and 100 for:');
|
||||
lines.push('- ' + invalidEntries.join('\n- '));
|
||||
}
|
||||
if (missingEntries.length) {
|
||||
lines.push('Missing entries (fill or check "Missing ok"):');
|
||||
lines.push('- ' + missingEntries.join('\n- '));
|
||||
}
|
||||
const warningBox = document.getElementById('missingWarning');
|
||||
const warningBody = warningBox ? warningBox.querySelector('.missing-warning-body') : null;
|
||||
if (warningBox && warningBody) {
|
||||
warningBody.textContent = lines.join('\n');
|
||||
warningBox.classList.remove('d-none');
|
||||
warningBox.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
} else {
|
||||
alert(lines.join('\n'));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,612 @@
|
||||
// /assets/js/validate_student.js
|
||||
import { validateNameLive } from './name_validation.js';
|
||||
import { validateEmailLive } from './email_validation.js';
|
||||
import { validatePhoneLive } from './phone_validation.js';
|
||||
import { validateAgeLive } from './age_validation.js';
|
||||
|
||||
(function () {
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var form = document.getElementById('studentRegistrationForm');
|
||||
if (!form) return;
|
||||
|
||||
// ---- Polyfills / helpers ----
|
||||
var CSS_ESCAPE = (window.CSS && typeof window.CSS.escape === 'function')
|
||||
? window.CSS.escape
|
||||
: function (s) { return String(s).replace(/[^a-zA-Z0-9_\-]/g, '\\$&'); };
|
||||
|
||||
function log() { try { console.log.apply(console, arguments); } catch (_) { } }
|
||||
function warn() { try { console.warn.apply(console, arguments); } catch (_) { } }
|
||||
function err() { try { console.error.apply(console, arguments); } catch (_) { } }
|
||||
|
||||
// ---- Config ----
|
||||
var MIN_AGE = 5;
|
||||
var MAX_AGE = 18;
|
||||
|
||||
var maxStudents = parseInt(form.dataset.maxStudents, 10) || 6;
|
||||
var existingStudents = parseInt(form.dataset.existingStudents, 10) || 0;
|
||||
var maxEmergency = parseInt(form.dataset.maxEmergency, 10) || 1;
|
||||
var existingEmergencies = parseInt(form.dataset.existingEmergencies, 10) || 0;
|
||||
|
||||
// Only allow emergency forms if there are NO registered students in DB
|
||||
var allowEmergency = (existingStudents === 0);
|
||||
|
||||
// ---- DOM ----
|
||||
var container = document.getElementById('studentFormsContainer');
|
||||
var emergencyContainer = document.getElementById('emergencyFormsContainer');
|
||||
var addStudentBtn = document.getElementById('addStudentBtn');
|
||||
var addAnotherStudentBtn = document.getElementById('addAnotherStudentBtn');
|
||||
var doneWithStudentBtn = document.getElementById('doneWithStudentBtn');
|
||||
var saveFormBtn = document.getElementById('saveFormBtn'); // should be type="submit"
|
||||
var initialButtons = document.getElementById('initialButtons');
|
||||
var formActionButtons = document.getElementById('formActionButtons');
|
||||
var studentTmplWrap = document.getElementById('studentFormTemplate');
|
||||
var contactTmplWrap = document.getElementById('emergencyFormTemplate');
|
||||
|
||||
var addedStudents = 0;
|
||||
var addedEmergencies = 0;
|
||||
var isSubmitting = false; // re-entrancy guard
|
||||
|
||||
|
||||
// ---- Group validators (at least one required; 'Other' requires text) ----
|
||||
function ensureFeedback(container, idBase, message) {
|
||||
var fb = container.querySelector('.invalid-feedback[data-for="' + idBase + '"]');
|
||||
if (!fb) {
|
||||
fb = document.createElement('div');
|
||||
fb.className = 'invalid-feedback';
|
||||
fb.setAttribute('data-for', idBase);
|
||||
container.appendChild(fb);
|
||||
}
|
||||
fb.textContent = message || '';
|
||||
fb.style.display = message ? 'block' : 'none';
|
||||
return fb;
|
||||
}
|
||||
|
||||
function validateChecklistRequired(block, baseName, otherInputName, labelText) {
|
||||
var groupWrap = block.querySelector('[data-group="' + baseName + '"]') || block; // optional wrapper
|
||||
var boxes = block.querySelectorAll('input[type="checkbox"][data-base-name="' + baseName + '"]');
|
||||
if (!boxes.length) return true;
|
||||
var idBase = baseName + '-' + (block.dataset.index || '');
|
||||
|
||||
var anyChecked = Array.prototype.some.call(boxes, function (b) { return b.checked; });
|
||||
var firstBox = boxes[0];
|
||||
var valid = true;
|
||||
var msg = '';
|
||||
|
||||
if (!anyChecked) {
|
||||
valid = false;
|
||||
msg = 'Please select at least one ' + (labelText || baseName.replace('_', ' ')) + '.';
|
||||
} else {
|
||||
// If "Other" is checked, ensure text provided
|
||||
var otherChecked = Array.prototype.some.call(boxes, function (b) { return /Other$/i.test(b.value) && b.checked; });
|
||||
if (otherChecked) {
|
||||
var otherInput = block.querySelector(
|
||||
'input[name="' + otherInputName + '"], input[data-other-for="' + baseName + '"]'
|
||||
);
|
||||
if (otherInput) {
|
||||
var val = (otherInput.value || '').trim();
|
||||
if (!val) {
|
||||
valid = false;
|
||||
msg = "Please specify the 'Other' " + (labelText || baseName.replace('_', ' ')) + '.';
|
||||
// mark 'other' input invalid too
|
||||
otherInput.classList.add('is-invalid');
|
||||
otherInput.setCustomValidity(msg);
|
||||
} else {
|
||||
otherInput.classList.remove('is-invalid');
|
||||
otherInput.setCustomValidity('');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// apply validity to first checkbox (so browser can focus it)
|
||||
if (!valid) {
|
||||
firstBox.classList.add('is-invalid');
|
||||
firstBox.setCustomValidity(msg);
|
||||
try { firstBox.scrollIntoView({ behavior: 'smooth', block: 'center' }); } catch (_) { }
|
||||
} else {
|
||||
boxes.forEach(function (b) { b.classList.remove('is-invalid'); b.setCustomValidity(''); });
|
||||
|
||||
var otherInput = block.querySelector(
|
||||
'input[name="' + otherInputName + '"], input[data-other-for="' + baseName + '"]'
|
||||
);
|
||||
if (otherInput) { otherInput.classList.remove('is-invalid'); otherInput.setCustomValidity(''); }
|
||||
}
|
||||
|
||||
// show/hide feedback message
|
||||
ensureFeedback(groupWrap, idBase, msg);
|
||||
return valid;
|
||||
}
|
||||
|
||||
// ---- Utilities ----
|
||||
function cloneTemplateContent(wrapper) {
|
||||
var temp = document.createElement('div');
|
||||
temp.innerHTML = (wrapper && wrapper.innerHTML ? wrapper.innerHTML : '').trim();
|
||||
return temp.firstElementChild || null;
|
||||
}
|
||||
|
||||
function showToast(message, type) {
|
||||
type = type || 'success';
|
||||
var toast = document.createElement('div');
|
||||
toast.className = 'alert alert-' + type + ' position-fixed top-0 end-0 m-3';
|
||||
toast.style.zIndex = '9999';
|
||||
toast.textContent = message;
|
||||
document.body.appendChild(toast);
|
||||
setTimeout(function () { if (toast && toast.parentNode) toast.parentNode.removeChild(toast); }, 3000);
|
||||
}
|
||||
|
||||
function reindexForms() {
|
||||
var forms = container.querySelectorAll('.student-form');
|
||||
forms.forEach(function (formEl, index) {
|
||||
formEl.querySelectorAll('[data-base-name]').forEach(function (input) {
|
||||
var base = input.dataset.baseName;
|
||||
if (input.type === 'radio') {
|
||||
input.name = base + '_' + index;
|
||||
input.id = base + '_' + index + '_' + input.value;
|
||||
} else if (input.type === 'checkbox') {
|
||||
input.name = base + '[' + index + '][]';
|
||||
} else {
|
||||
input.name = base + '[' + index + ']';
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function updateButtonStates() {
|
||||
var totalStudents = existingStudents + addedStudents;
|
||||
var remainingStudents = maxStudents - totalStudents;
|
||||
var hasStudentForms = container.querySelectorAll('.student-form').length > 0;
|
||||
|
||||
// Reset
|
||||
if (initialButtons) initialButtons.classList.add('d-none');
|
||||
if (formActionButtons) formActionButtons.classList.add('d-none');
|
||||
if (saveFormBtn) saveFormBtn.classList.add('d-none');
|
||||
if (doneWithStudentBtn) doneWithStudentBtn.classList.add('d-none');
|
||||
|
||||
// 1) no existing + no draft -> show initial Add
|
||||
if (existingStudents === 0 && !hasStudentForms) {
|
||||
if (initialButtons) initialButtons.classList.remove('d-none');
|
||||
if (addStudentBtn) addStudentBtn.classList.remove('d-none');
|
||||
return;
|
||||
}
|
||||
|
||||
// 2) existing students present + no draft -> show "Add Another"
|
||||
if (existingStudents > 0 && !hasStudentForms) {
|
||||
if (formActionButtons) formActionButtons.classList.remove('d-none');
|
||||
if (addAnotherStudentBtn) {
|
||||
addAnotherStudentBtn.classList.remove('d-none');
|
||||
var atMax = (totalStudents >= maxStudents);
|
||||
addAnotherStudentBtn.disabled = atMax;
|
||||
addAnotherStudentBtn.title = atMax
|
||||
? ('Maximum of ' + maxStudents + ' students reached')
|
||||
: ('Add student (' + remainingStudents + ' remaining)');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 3) at least one draft student form
|
||||
if (formActionButtons) formActionButtons.classList.remove('d-none');
|
||||
if (doneWithStudentBtn) doneWithStudentBtn.classList.remove('d-none');
|
||||
|
||||
var atMaxStudents = totalStudents >= maxStudents;
|
||||
if (addAnotherStudentBtn) {
|
||||
addAnotherStudentBtn.disabled = atMaxStudents;
|
||||
addAnotherStudentBtn.title = atMaxStudents
|
||||
? ('Maximum of ' + maxStudents + ' students reached')
|
||||
: ('Add student (' + remainingStudents + ' remaining)');
|
||||
if (atMaxStudents && !hasStudentForms) {
|
||||
addAnotherStudentBtn.classList.add('d-none');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function initializeLiveValidation(scope) {
|
||||
scope.querySelectorAll('input[data-base-name="studentFirstName"], input[data-base-name="studentLastName"], input[data-base-name="emergency_firstname"], input[data-base-name="emergency_lastname"]').forEach(function (el) {
|
||||
el.addEventListener('input', function () { validateNameLive(el); });
|
||||
el.title = 'Only letters, spaces, or dashes (2–30 chars)';
|
||||
});
|
||||
|
||||
scope.querySelectorAll('input[data-base-name="emergency_email"]').forEach(function (el) {
|
||||
el.addEventListener('input', function () { validateEmailLive(el); });
|
||||
el.title = 'Email must be in the format: user@example.com';
|
||||
});
|
||||
|
||||
scope.querySelectorAll('input[data-base-name="emergency_phone"]').forEach(function (el) {
|
||||
el.addEventListener('input', function () { validatePhoneLive(el); });
|
||||
el.title = 'Phone number must be 10 digits, e.g., 123-456-7890';
|
||||
});
|
||||
|
||||
scope.querySelectorAll('input[data-base-name="dob"]').forEach(function (el) {
|
||||
el.addEventListener('input', function () { validateAgeLive(el, MIN_AGE, MAX_AGE); });
|
||||
el.title = 'Age must be between ' + MIN_AGE + ' and ' + MAX_AGE;
|
||||
});
|
||||
wireChecklistLiveValidation(scope);
|
||||
}
|
||||
|
||||
function validateRadioGroup(scope, baseName) {
|
||||
var radios = scope.querySelectorAll('input[data-base-name="' + baseName + '"][type="radio"]');
|
||||
if (!radios.length) return true;
|
||||
|
||||
var names = Array.from(new Set(Array.from(radios).map(function (r) { return r.name; })));
|
||||
var ok = true;
|
||||
|
||||
names.forEach(function (n) {
|
||||
var group = scope.querySelectorAll('input[name="' + CSS_ESCAPE(n) + '"]');
|
||||
var oneChecked = Array.from(group).some(function (r) { return r.checked; });
|
||||
group.forEach(function (r) { r.setCustomValidity(oneChecked ? '' : 'Please select an option.'); });
|
||||
ok = ok && oneChecked;
|
||||
});
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
function findFirstInvalid(formEl) {
|
||||
var candidates = formEl.querySelectorAll('input, select, textarea');
|
||||
for (var i = 0; i < candidates.length; i++) {
|
||||
var el = candidates[i];
|
||||
if (!el.checkValidity()) return el;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function setDefaultsForNewStudentForm(formEl) {
|
||||
// Do NOT preselect any radio
|
||||
formEl.querySelectorAll('input[type="radio"][data-base-name="last_year"]').forEach(function (r) {
|
||||
r.checked = false;
|
||||
});
|
||||
|
||||
// Keep grade select disabled until a choice is made
|
||||
var gradeSelect = formEl.querySelector('.grade-select');
|
||||
var gradeLabel = formEl.querySelector('.grade-label');
|
||||
if (gradeSelect && gradeLabel) {
|
||||
gradeSelect.innerHTML = '<option value="">Select Grade</option>';
|
||||
gradeSelect.disabled = true;
|
||||
gradeLabel.innerHTML = 'Last Year Grade <span class="text-danger">*</span>';
|
||||
}
|
||||
}
|
||||
|
||||
function addStudentForm() {
|
||||
if (existingStudents + addedStudents >= maxStudents) {
|
||||
showToast('Maximum of ' + maxStudents + ' students reached', 'info');
|
||||
updateButtonStates();
|
||||
return false;
|
||||
}
|
||||
var clone = cloneTemplateContent(studentTmplWrap);
|
||||
if (!clone) return false;
|
||||
|
||||
// ✅ ensure marker class exists
|
||||
if (!clone.classList.contains('student-form')) clone.classList.add('student-form');
|
||||
|
||||
container.classList.remove('d-none');
|
||||
container.appendChild(clone);
|
||||
addedStudents++;
|
||||
|
||||
reindexForms();
|
||||
setDefaultsForNewStudentForm(clone);
|
||||
initializeLiveValidation(clone);
|
||||
updateButtonStates();
|
||||
return true;
|
||||
}
|
||||
|
||||
function addEmergencyForm() {
|
||||
if (!allowEmergency) return false;
|
||||
if (existingEmergencies + addedEmergencies >= maxEmergency) return false;
|
||||
|
||||
var clone = cloneTemplateContent(contactTmplWrap);
|
||||
if (!clone) return false;
|
||||
|
||||
// ✅ ensure marker class exists
|
||||
if (!clone.classList.contains('emergency-contact-form')) clone.classList.add('emergency-contact-form');
|
||||
|
||||
emergencyContainer.classList.remove('d-none');
|
||||
emergencyContainer.appendChild(clone);
|
||||
addedEmergencies++;
|
||||
|
||||
initializeLiveValidation(clone);
|
||||
updateButtonStates();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Disable emergency inputs that shouldn’t participate in THIS submit
|
||||
function neutralizeEmergencyBlocksForSubmit(formEl, allow) {
|
||||
var blocks = formEl.querySelectorAll('.emergency-contact-form');
|
||||
var disabledInputs = [];
|
||||
if (!blocks.length) return { disabledInputs: disabledInputs };
|
||||
|
||||
if (!allow) {
|
||||
blocks.forEach(function (block) {
|
||||
block.querySelectorAll('input, select, textarea').forEach(function (el) {
|
||||
if (!el.disabled) {
|
||||
el.disabled = true;
|
||||
disabledInputs.push(el);
|
||||
}
|
||||
el.setCustomValidity('');
|
||||
});
|
||||
});
|
||||
return { disabledInputs: disabledInputs };
|
||||
}
|
||||
|
||||
// allow === true: disable completely empty blocks so 'required' won't trigger
|
||||
blocks.forEach(function (block) {
|
||||
var fields = block.querySelectorAll('input, select, textarea');
|
||||
var anyFilled = Array.from(fields).some(function (el) { return (el.value || '').trim().length > 0; });
|
||||
if (!anyFilled) {
|
||||
fields.forEach(function (el) {
|
||||
if (!el.disabled) {
|
||||
el.disabled = true;
|
||||
disabledInputs.push(el);
|
||||
}
|
||||
el.setCustomValidity('');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return { disabledInputs: disabledInputs };
|
||||
}
|
||||
|
||||
// ---- Listeners ----
|
||||
if (addStudentBtn && studentTmplWrap) {
|
||||
addStudentBtn.addEventListener('click', function () {
|
||||
if (addStudentForm()) updateButtonStates();
|
||||
});
|
||||
}
|
||||
|
||||
if (addAnotherStudentBtn && studentTmplWrap) {
|
||||
addAnotherStudentBtn.addEventListener('click', function () {
|
||||
if (addStudentForm()) updateButtonStates();
|
||||
});
|
||||
}
|
||||
|
||||
if (doneWithStudentBtn) {
|
||||
doneWithStudentBtn.addEventListener('click', function () {
|
||||
var hasEmergencyForms = emergencyContainer.querySelectorAll('.emergency-contact-form').length > 0;
|
||||
if (allowEmergency && !hasEmergencyForms) addEmergencyForm(); // first-time family
|
||||
if (saveFormBtn) saveFormBtn.classList.remove('d-none');
|
||||
doneWithStudentBtn.classList.add('d-none');
|
||||
});
|
||||
}
|
||||
|
||||
// --- Delete student (SAFE: only intercept draft .student-form) ---
|
||||
function handleRemoveStudentClick(e) {
|
||||
const btn = e.target.closest('.js-remove-student');
|
||||
if (!btn) return;
|
||||
|
||||
// If the button is NOT inside a draft student form, let native behavior proceed
|
||||
const draftBlock = btn.closest('.student-form');
|
||||
if (!draftBlock) return;
|
||||
|
||||
// Draft delete: prevent submit and remove the block
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (draftBlock.parentNode) draftBlock.parentNode.removeChild(draftBlock);
|
||||
addedStudents = Math.max(0, addedStudents - 1);
|
||||
|
||||
reindexForms();
|
||||
updateButtonStates();
|
||||
|
||||
if (!container.querySelectorAll('.student-form').length) {
|
||||
if (emergencyContainer) {
|
||||
emergencyContainer.innerHTML = '';
|
||||
addedEmergencies = 0;
|
||||
updateButtonStates();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Attach listeners (capture = true to beat other handlers)
|
||||
if (container) container.addEventListener('click', handleRemoveStudentClick, true);
|
||||
document.addEventListener('click', handleRemoveStudentClick, true);
|
||||
|
||||
if (emergencyContainer) {
|
||||
emergencyContainer.addEventListener('click', function (e) {
|
||||
var btn = e.target.closest('.js-remove-contact');
|
||||
if (!btn) return;
|
||||
var block = btn.closest('.emergency-contact-form');
|
||||
if (!block) return;
|
||||
|
||||
block.remove();
|
||||
addedEmergencies = Math.max(0, addedEmergencies - 1);
|
||||
updateButtonStates();
|
||||
});
|
||||
}
|
||||
|
||||
// Enable grade select only after the last_year radio is chosen
|
||||
container.addEventListener('change', function (e) {
|
||||
var target = e.target;
|
||||
if (target && target.matches('input[type="radio"][data-base-name="last_year"]') && target.checked) {
|
||||
var studentForm = target.closest('.student-form');
|
||||
if (!studentForm) return;
|
||||
|
||||
if (typeof window.updateGradeOptions === 'function') {
|
||||
try { window.updateGradeOptions(studentForm, target.value); } catch (ex) { warn('updateGradeOptions error', ex); }
|
||||
} else {
|
||||
var gradeSelect = studentForm.querySelector('.grade-select');
|
||||
if (gradeSelect) gradeSelect.disabled = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// ---- Submit & Validation ----
|
||||
form.addEventListener('submit', function (e) {
|
||||
try {
|
||||
console.log('submit handler: start', { action: form.action, method: form.method });
|
||||
|
||||
if (isSubmitting) {
|
||||
console.warn('submit handler: already submitting — ignored');
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable empty/blocked emergency inputs for THIS attempt
|
||||
const { disabledInputs } = neutralizeEmergencyBlocksForSubmit(form, allowEmergency);
|
||||
|
||||
// ✅ Robust presence detection based on enabled fields inside containers
|
||||
const studentInputs = form.querySelectorAll(
|
||||
'#studentFormsContainer input, #studentFormsContainer select, #studentFormsContainer textarea'
|
||||
);
|
||||
const emergencyInputs = form.querySelectorAll(
|
||||
'#emergencyFormsContainer input, #emergencyFormsContainer select, #emergencyFormsContainer textarea'
|
||||
);
|
||||
|
||||
const hasStudentForms = Array.from(studentInputs).some(el => !el.disabled);
|
||||
const hasEmergencyForms = allowEmergency && Array.from(emergencyInputs).some(el => !el.disabled);
|
||||
|
||||
if (!hasStudentForms && !hasEmergencyForms) {
|
||||
const proceed = confirm('No student or emergency forms detected. Submit anyway?');
|
||||
if (!proceed) {
|
||||
e.preventDefault();
|
||||
// re-enable so user can edit
|
||||
disabledInputs.forEach(el => el.disabled = false);
|
||||
console.warn('submit handler: user canceled — no forms present', {
|
||||
studentInputs: studentInputs.length,
|
||||
studentEnabled: Array.from(studentInputs).filter(el => !el.disabled).length,
|
||||
emergencyInputs: emergencyInputs.length,
|
||||
emergencyEnabled: Array.from(emergencyInputs).filter(el => !el.disabled).length,
|
||||
allowEmergency
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let allValid = true;
|
||||
|
||||
// Validate non-emergency inputs (per your existing rules)
|
||||
form.querySelectorAll('input[data-base-name]:not([data-base-name^="emergency_"])').forEach((input) => {
|
||||
const base = input.dataset.baseName;
|
||||
let valid = true;
|
||||
if (base === 'studentFirstName' || base === 'studentLastName') valid = validateNameLive(input);
|
||||
else if (base === 'dob') valid = validateAgeLive(input, MIN_AGE, MAX_AGE);
|
||||
input.setCustomValidity(valid ? '' : 'Please fix this field.');
|
||||
allValid = allValid && valid;
|
||||
});
|
||||
|
||||
// Required selects (skip disabled)
|
||||
form.querySelectorAll('select[required]').forEach((sel) => {
|
||||
if (sel.disabled) { sel.setCustomValidity(''); return; }
|
||||
const ok = sel.value !== '';
|
||||
sel.setCustomValidity(ok ? '' : 'Please choose an option.');
|
||||
allValid = allValid && ok;
|
||||
});
|
||||
|
||||
// Radio groups per student (e.g., last_year)
|
||||
form.querySelectorAll('.student-form').forEach((sf) => {
|
||||
const radiosOk = validateRadioGroup(sf, 'last_year');
|
||||
allValid = allValid && radiosOk;
|
||||
});
|
||||
|
||||
// ---- Validate allergies & medical conditions for each student ----
|
||||
let groupOk = true;
|
||||
form.querySelectorAll('.student-form').forEach(function (block, idx) {
|
||||
block.dataset.index = String(idx); // used for feedback id base
|
||||
const ok1 = validateChecklistRequired(block, 'allergies', 'allergy_other[]', 'allergy');
|
||||
const ok2 = validateChecklistRequired(block, 'medical_conditions', 'medical_condition_other[]', 'medical condition');
|
||||
if (!ok1 || !ok2) groupOk = false;
|
||||
});
|
||||
allValid = allValid && groupOk;
|
||||
|
||||
// Emergency (allowed + non-empty blocks only)
|
||||
if (allowEmergency) {
|
||||
form.querySelectorAll('.emergency-contact-form').forEach(block => {
|
||||
const anyEnabled = block.querySelector('input:not(:disabled), select:not(:disabled), textarea:not(:disabled)');
|
||||
if (!anyEnabled) return; // empty block was disabled above
|
||||
|
||||
block.querySelectorAll('input[data-base-name]').forEach(input => {
|
||||
const base = input.dataset.baseName;
|
||||
let valid = true;
|
||||
if (base === 'emergency_firstname' || base === 'emergency_lastname') valid = validateNameLive(input);
|
||||
else if (base === 'emergency_email') valid = validateEmailLive(input);
|
||||
else if (base === 'emergency_phone') valid = validatePhoneLive(input);
|
||||
input.setCustomValidity(valid ? '' : 'Please fix this field.');
|
||||
allValid = allValid && valid;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Final check
|
||||
if (!form.checkValidity() || !allValid) {
|
||||
e.preventDefault();
|
||||
const firstBad = (function findFirstInvalid(formEl) {
|
||||
const nodes = formEl.querySelectorAll('input, select, textarea');
|
||||
for (const el of nodes) { if (!el.checkValidity()) return el; }
|
||||
return null;
|
||||
})(form);
|
||||
if (firstBad) {
|
||||
console.warn('First invalid:', firstBad, firstBad.name, firstBad.dataset && firstBad.dataset.baseName, firstBad.validationMessage);
|
||||
try { firstBad.scrollIntoView({ behavior: 'smooth', block: 'center' }); } catch (_) { }
|
||||
}
|
||||
form.reportValidity();
|
||||
disabledInputs.forEach(el => el.disabled = false); // keep editable
|
||||
console.warn('submit handler: blocked — invalid');
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ SUCCESS: post once, outside this tick (bypass submit handlers)
|
||||
e.preventDefault();
|
||||
isSubmitting = true;
|
||||
console.log('submit handler: valid — posting via form.submit()');
|
||||
setTimeout(function () { form.submit(); }, 0);
|
||||
|
||||
} catch (ex) {
|
||||
console.error('submit handler: exception', ex);
|
||||
try { e.preventDefault(); } catch (_) { }
|
||||
}
|
||||
});
|
||||
|
||||
function revalidateChecklistGroup(el) {
|
||||
const block = el.closest('.student-form');
|
||||
if (!block) return;
|
||||
// Ensure stable id base
|
||||
if (!block.dataset.index) {
|
||||
const list = Array.from(block.parentNode.querySelectorAll('.student-form'));
|
||||
block.dataset.index = String(list.indexOf(block));
|
||||
}
|
||||
// Re-validate both groups (safe + simple)
|
||||
validateChecklistRequired(block, 'allergies', 'allergy_other[]', 'allergy');
|
||||
validateChecklistRequired(block, 'medical_conditions', 'medical_condition_other[]', 'medical condition');
|
||||
}
|
||||
|
||||
function wireChecklistLiveValidation(scope) {
|
||||
// Check/uncheck boxes
|
||||
scope.addEventListener('change', function (e) {
|
||||
const t = e.target;
|
||||
if (!t) return;
|
||||
if (t.matches('input[type="checkbox"][data-base-name="allergies"]') ||
|
||||
t.matches('input[type="checkbox"][data-base-name="medical_conditions"]')) {
|
||||
revalidateChecklistGroup(t);
|
||||
}
|
||||
});
|
||||
|
||||
// Typing in the "Other" input
|
||||
scope.addEventListener('input', function (e) {
|
||||
const t = e.target;
|
||||
if (!t) return;
|
||||
if (t.matches('input[name="allergy_other[]"], input[name="medical_condition_other[]"], input[data-other-for="allergies"], input[data-other-for="medical_conditions"]')) {
|
||||
revalidateChecklistGroup(t);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// ---- Init ----
|
||||
initializeLiveValidation(form);
|
||||
wireChecklistLiveValidation(form);
|
||||
updateButtonStates();
|
||||
|
||||
// If server repopulated radios, reflect in grade select
|
||||
container.querySelectorAll('.student-form').forEach(function (sf) {
|
||||
var checked = sf.querySelector('input[type="radio"][data-base-name="last_year"]:checked');
|
||||
if (checked && typeof window.updateGradeOptions === 'function') {
|
||||
try { window.updateGradeOptions(sf, checked.value); } catch (ex) { warn('updateGradeOptions init error', ex); }
|
||||
}
|
||||
});
|
||||
|
||||
log('validate_student.js loaded', {
|
||||
maxStudents: maxStudents,
|
||||
existingStudents: existingStudents,
|
||||
maxEmergency: maxEmergency,
|
||||
existingEmergencies: existingEmergencies,
|
||||
allowEmergency: allowEmergency
|
||||
});
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,171 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
/* ---------- helpers ---------- */
|
||||
const form = document.getElementById('registrationForm'); // <— pick the specific form
|
||||
if (!form) { // <— bail out if not on this page
|
||||
console.debug('validation.js: no #registrationForm — skipping init');
|
||||
return;
|
||||
}
|
||||
|
||||
const el = name => form.querySelector(`[name="${name}"]`);
|
||||
const allInputs = form.querySelectorAll('input, select');
|
||||
|
||||
/* ---------- regex rules ---------- */
|
||||
const nameRE = /^[A-Za-z\s-]{2,30}$/;
|
||||
const phoneRE = /^\(?\d{3}\)?[-\s]?\d{3}[-\s]?\d{4}$/;
|
||||
const emailRE = /^(?=.{2,50}$)[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/;
|
||||
const streetRE = /^[A-Za-z0-9.\s-]{2,30}$/;
|
||||
const aptRE = /^[A-Za-z0-9.\s-]{0,15}$/;
|
||||
const cityRE = /^[A-Za-z0-9\s.'-]{2,15}$/;
|
||||
const zipRE = /^\d{5}$/;
|
||||
const captchaRE= /^[A-Za-z0-9]{4,10}$/;
|
||||
|
||||
const formatHints = {
|
||||
firstname: "Only letters, spaces, or dashes",
|
||||
lastname: "Only letters, spaces, or dashes",
|
||||
email: "Format: user@example.com",
|
||||
confirm_email: "Must match the email exactly",
|
||||
cellphone: "Exactly 10 digits (e.g., 1234567890 , (123) 456-4789)",
|
||||
address_street: "Letters, numbers, dots, dashes",
|
||||
apt: "Optional - Only letters, spaces, or dashes",
|
||||
city: "Letters and spaces",
|
||||
zip: "Exactly 5 digits (e.g., 12345)",
|
||||
captcha: "4–10 letters or digits"
|
||||
};
|
||||
|
||||
const validateField = (input) => {
|
||||
const v = input.value.trim();
|
||||
let ok = true;
|
||||
|
||||
switch (input.name) {
|
||||
case 'firstname':
|
||||
case 'lastname':
|
||||
case 'second_firstname':
|
||||
case 'second_lastname':
|
||||
ok = nameRE.test(v); break;
|
||||
case 'cellphone':
|
||||
case 'second_cellphone':
|
||||
ok = phoneRE.test(v); break;
|
||||
case 'email':
|
||||
case 'second_email':
|
||||
ok = emailRE.test(v); break;
|
||||
case 'confirm_email':
|
||||
ok = v === (el('email')?.value ?? ''); break; // <— guard
|
||||
case 'address_street':
|
||||
ok = streetRE.test(v); break;
|
||||
case 'apt':
|
||||
ok = !v || aptRE.test(v); break;
|
||||
case 'city':
|
||||
ok = cityRE.test(v); break;
|
||||
case 'zip':
|
||||
ok = zipRE.test(v); break;
|
||||
case 'captcha':
|
||||
ok = captchaRE.test(v); break;
|
||||
}
|
||||
input.classList.toggle('is-invalid', !ok);
|
||||
return ok;
|
||||
};
|
||||
|
||||
/* ---------- live validation & re-check confirm_email ---------- */
|
||||
allInputs.forEach(inp => {
|
||||
const hint = document.getElementById(`${inp.name}-hint`);
|
||||
|
||||
inp.addEventListener('input', () => {
|
||||
if (inp.disabled) return;
|
||||
|
||||
validateField(inp);
|
||||
|
||||
// Show hint while typing
|
||||
if (hint) hint.classList.remove('d-none');
|
||||
|
||||
// Special revalidation for confirm_email
|
||||
if (inp.name === 'email') {
|
||||
const conf = el('confirm_email');
|
||||
if (conf && !conf.disabled) validateField(conf);
|
||||
}
|
||||
});
|
||||
|
||||
inp.addEventListener('blur', () => {
|
||||
const hint = document.getElementById(`${inp.name}-hint`);
|
||||
if (hint) hint.classList.add('d-none');
|
||||
});
|
||||
});
|
||||
|
||||
/* ---------- copy / paste blocking with inline warnings ---------- */
|
||||
const blockPasteConfig = [
|
||||
{ id: 'confirm_email', warnId: 'copy-warning-email',
|
||||
msg: 'Copy/paste is disabled for this field. Please type the answer.' },
|
||||
{ id: 'captcha', warnId: 'captcha-warning',
|
||||
msg: 'Copy/paste is disabled for this field. Please type the answer.' }
|
||||
];
|
||||
|
||||
blockPasteConfig.forEach(({ id, warnId, msg }) => {
|
||||
const input = document.getElementById(id);
|
||||
if (!input) return;
|
||||
|
||||
let warnBox = document.getElementById(warnId);
|
||||
if (!warnBox) {
|
||||
warnBox = document.createElement('div');
|
||||
warnBox.id = warnId;
|
||||
warnBox.className = 'invalid-feedback d-none';
|
||||
warnBox.textContent = msg;
|
||||
input.parentElement.appendChild(warnBox);
|
||||
}
|
||||
|
||||
const showWarn = (e) => {
|
||||
e.preventDefault();
|
||||
warnBox.classList.remove('d-none');
|
||||
input.classList.add('is-invalid');
|
||||
setTimeout(() => {
|
||||
warnBox.classList.add('d-none');
|
||||
input.classList.remove('is-invalid');
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
['paste', 'copy', 'cut'].forEach(evt => input.addEventListener(evt, showWarn));
|
||||
});
|
||||
|
||||
/* ---------- second-parent toggle logic ---------- */
|
||||
const isParentCheckbox = document.getElementById('is_parent');
|
||||
const secondSection = document.getElementById('second-parent-section');
|
||||
const noSecondToggle = document.getElementById('no_second_parent_info');
|
||||
const secondParentFields = document.getElementById('second-parent-fields');
|
||||
|
||||
const setSecondDisabled = (dis) => {
|
||||
if (!secondParentFields) return; // <— guard
|
||||
secondParentFields.querySelectorAll('input, select').forEach(f => {
|
||||
f.disabled = dis;
|
||||
if (dis) f.classList.remove('is-invalid');
|
||||
});
|
||||
};
|
||||
|
||||
const refreshSecondUI = () => {
|
||||
if (!secondSection) return; // <— guard
|
||||
const parent = !!isParentCheckbox?.checked;
|
||||
const noInfo = !!noSecondToggle?.checked;
|
||||
secondSection.style.display = parent ? 'block' : 'none';
|
||||
setSecondDisabled(!parent || noInfo);
|
||||
};
|
||||
|
||||
isParentCheckbox?.addEventListener('change', refreshSecondUI);
|
||||
noSecondToggle?.addEventListener('change', refreshSecondUI);
|
||||
refreshSecondUI();
|
||||
|
||||
/* ---------- submit event ---------- */
|
||||
form.addEventListener('submit', e => {
|
||||
let ok = true;
|
||||
|
||||
allInputs.forEach(inp => {
|
||||
if (inp.disabled) return;
|
||||
|
||||
const inSecond = secondParentFields && inp.closest('#second-parent-fields');
|
||||
if (inSecond && (!isParentCheckbox?.checked || noSecondToggle?.checked)) return;
|
||||
|
||||
if (!validateField(inp)) ok = false;
|
||||
});
|
||||
|
||||
if (!ok) {
|
||||
e.preventDefault();
|
||||
form.querySelector('.is-invalid')?.focus({ preventScroll: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
<div id="student-form-template" class="d-none">
|
||||
<div class="student-form">
|
||||
|
||||
<h4 class="form-title">Student-# Information</h4>
|
||||
|
||||
<div class="mb-3">
|
||||
<label>Was your child with us last year? *</label><br>
|
||||
<input type="radio" class="lastYearRadio" name="last_year_#" value="yes" required> Yes
|
||||
<input type="radio" class="lastYearRadio" name="last_year_#" value="no" required> No
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label>First Name</label>
|
||||
<input type="text" name="studentFirstName[]" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label>Last Name</label>
|
||||
<input type="text" name="studentLastName[]" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label>Date of Birth</label>
|
||||
<input type="date" name="dob[]" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label>School Grade</label>
|
||||
<select name="registration_grade[]" class="form-select dynamic-grade" required>
|
||||
<option value="">Select School Grade</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label>Gender</label>
|
||||
<select name="gender[]" class="form-select" required>
|
||||
<option value="">Select Gender</option>
|
||||
<option value="Male">Male</option>
|
||||
<option value="Female">Female</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="text-end">
|
||||
<button type="button" class="btn btn-danger remove-student">Remove</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||