recreate project

This commit is contained in:
root
2026-02-10 22:11:06 -05:00
commit 663c0cdbda
10149 changed files with 1379710 additions and 0 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+597
View File
@@ -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 */
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+594
View File
@@ -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 */
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+443
View File
@@ -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;
}
+332
View File
@@ -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;
}
+83
View File
@@ -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;
}
+155
View File
@@ -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;
}
+64
View File
@@ -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;
}
}
+626
View File
@@ -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;
}
}
+267
View File
@@ -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;
}
+119
View File
@@ -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;
}
}
+24
View File
@@ -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;
}
+156
View File
@@ -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;
}
}
+813
View File
@@ -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);
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 452 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 540 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 373 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 724 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

+4
View File
@@ -0,0 +1,4 @@
$(document).ready(function(){
$('#birth-date').mask('00/00/0000');
$('#phone-number').mask('0000-0000');
})
+73
View File
@@ -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()})`
);
});
});
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+4450
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+4497
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+21
View File
@@ -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;
}
+107
View File
@@ -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 ✅');
});
+31
View File
@@ -0,0 +1,31 @@
// name_validation.js
export function validateNameLive(inputEl) {
// Allow only letters, spaces, and dashes, 230 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 (230 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;
}
+74
View File
@@ -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();
});
+189
View File
@@ -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);
});
})();
+118
View File
@@ -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"));
}
});
});
+47
View File
@@ -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();
}
});
+247
View File
@@ -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;
+119
View File
@@ -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'));
}
}
});
});
+612
View File
@@ -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 (230 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 shouldnt 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
});
});
})();
+171
View File
@@ -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: "410 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>
Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More