diff --git a/2026/qual_config.js b/2026/qual_config.js
new file mode 100644
index 000000000..65188ff66
--- /dev/null
+++ b/2026/qual_config.js
@@ -0,0 +1,59 @@
+var qualitativeConfig = `
+{
+ "title": "Qualitative Scouting",
+ "page_title": "Robot Behavior",
+ "dataFormat": "tsv",
+ "checkboxAs": "10",
+ "prematch": {
+ "title": "Pre-Match",
+ "fields": [
+ { "name": "Scouter", "code": "s", "type": "text", "size": 10, "maxSize": 20, "subtype": "scouter" },
+ { "name": "Event", "code": "e", "type": "text", "size": 10, "maxSize": 20, "subtype": "event", "defaultValue": "2026TXHOU", "disabled": "true" },
+ { "name": "Match", "code": "m", "type": "number", "size": 3, "maxSize": 3 },
+ { "name": "Team #", "code": "t", "type": "number", "size": 5, "maxSize": 5 }
+ ]
+ },
+ "sections": [
+ {
+ "title": "Robot Behavior",
+ "fields": [
+ {
+ "name": "Driving Style",
+ "code": "ds",
+ "type": "radio",
+ "choices": { "ag": "Aggressive", "def": "Defensive", "cau": "Cautious", "oth": "Other" }
+ },
+ {
+ "name": "Defense Quality",
+ "code": "dq",
+ "type": "radio",
+ "choices": { "ex": "Excellent", "gd": "Good", "fa": "Fair", "po": "Poor", "na": "N/A" }
+ },
+ {
+ "name": "Recovery from Faults",
+ "code": "rf",
+ "type": "radio",
+ "choices": { "qu": "Quick", "sl": "Slow", "no": "No Recovery" }
+ },
+ {
+ "name": "Plays Well with Alliance",
+ "code": "pa",
+ "type": "checkbox"
+ },
+ {
+ "name": "Frequently Disabled/Tipped",
+ "code": "fd",
+ "type": "checkbox"
+ },
+ {
+ "name": "Notes",
+ "code": "n",
+ "type": "text",
+ "size": 40,
+ "maxSize": 200
+ }
+ ]
+ }
+ ]
+}
+`;
\ No newline at end of file
diff --git a/pit.html b/pit.html
index 11afb3a97..de662c1c2 100644
--- a/pit.html
+++ b/pit.html
@@ -1,63 +1,63 @@
-
-
-
-
- Scouting PASS
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+ Scouting PASS
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/qual.html b/qual.html
new file mode 100644
index 000000000..7d1bcdf83
--- /dev/null
+++ b/qual.html
@@ -0,0 +1,436 @@
+
+
+
+
+ Scouting PASS
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/css/scoutingPASS.css b/resources/css/scoutingPASS.css
index a1cb449c0..544786d11 100644
--- a/resources/css/scoutingPASS.css
+++ b/resources/css/scoutingPASS.css
@@ -1,359 +1,508 @@
-/* @font-face {
- font-family: 'Roboto';
- src: url('./fonts/Roboto-Regular.ttf') format('truetype');
- font-weight: 400;
- font-style: normal;
- font-display: swap;
-}
-*/
-
-html, body{
- height: 100%;
- width: 100%;
- margin: 0;
- padding: 0;
- color: white;
- background-color: black;
- font-family: Roboto, sans-serif;
-}
-
-#prematch_table, #postmatch_table {
-
- font-family : Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif !important;
- text-align: center;
- color: white;
- font-size: 13px;
- font-style: bold;
- font-weight: 700;
- letter-spacing: 1px;
- border-width: 4px;
- border-color: white;
-}
-
-#auton_table, #endgame_table, #teleop_table {
- font-family : Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif !important;
- color: black;
- font-size: 13px;
- font-style: bold;
- font-weight: 700;
- letter-spacing: 1px;
- border-width: 4px;
- border-color: black;
-}
-
-
-.button {
- font-family: Roboto;
- font-size: 16px;
- font-weight: 500;
-}
-
-.counter-button {
- width: 40px;
- height: 40px;
- font-size: 14px;
- padding: 5px;
- border-radius: 4px;
- background-color: #f6faff;
- color: black;
- border: black;
- border-width: 1px;
- border-style: solid;
- cursor: pointer;
-
- display: flex;
- justify-content: center;
- align-items: center;
-}
-
-/* hide the actual radio input */
-.radio-btn input[type="radio"] {
- display: none;
-}
-
-.radio-btn span {
- display: inline-block;
- padding: 6px 12px; /* size of the box */
- margin: 2px; /* spacing between buttons */
- border: 1px solid #444; /* box border */
- border-radius: 4px; /* rounded corners */
- cursor: pointer;
- user-select: none;
- text-align: center;
-}
-
-/* highlight when selected */
-.radio-btn input[type="radio"]:checked + span {
- background-color: rgb(239, 239, 236);
- color: black;
-}
-
-.radio-btn input[type="radio"][value^="r"] + span {
- border-color: #d9534f;
- border-style: solid;
- border-width: 1px;
- color: white;
-}
-
-.radio-btn input[type="radio"][value^="b"] + span {
- border-color: #0275d8;
- border-style: solid;
- border-width: 1px;
- color: white;
-}
-
-.radio-btn input[type="radio"][value^="r"]:checked + span {
- background-color: #d9534f;
- border-color: #d9534f;
- border-style: solid;
- border-width: 5px;
- color: white;
-}
-
-.radio-btn input[type="radio"][value^="b"]:checked + span {
- background-color: #0275d8;
- border-color: #0275d8;
- border-style: solid;
- border-width: 5px;
- color: white;
-}
-
-/* Style the label to look like a button */
-.check-btn {
- display: inline-block;
- padding: 15px 15px;
- margin: 5px;
- border-radius: 4px;
- border-color: black;
- border-style: solid;
- border-width: 1px;
- cursor: pointer;
- color: white;
- text-align: center;
- user-select: none; /* Disable text selection */
-}
-
-@media (max-width: 768px) {
- .check-btn {
- padding: 4vw 6vw; /* Adjust padding for mobile */
- }
-}
-
-/* Ensure no default checkbox styles */
-input[type="checkbox"] {
- display: none; /* Hide the checkbox, it will be visually represented by the label */
-}
-
-#autonHeader, #endgameHeader, #teleopHeader1, #teleopHeader2{
- color: black;
- border-color: black;
- border-width: 4px;
-}
-
-#postmatchHeader{
- border-width: 4px;
- border-color: white;
-}
-
-#prematchHeader1, #prematchHeader2{
- color: white;
- border-color: white;
- border-width: 4px;
-}
-
-#qrHeader1, #qrHeader2 {
- color: black;
-}
-
-.field-image {
- margin: 2vw;
-}
-
-.field-image-src {
- width: 225px;
- max-width: 500px;
- height: auto;
- background-position: center;
- border: 1px solid;
-}
-
-.main-panel{
- display: none;
- height: 100%;
- min-height: 100%;
- width: 100vw;
- position: absolute;
- left: 0;
- top: 0;
- color: white;
- background-color: black;
- font-family : Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif !important;
- text-align: justify;
-}
-
-#prematch{
- display: table;
-}
-
-#auton{
-}
-
-#teleop{
-}
-
-#endgame{
-}
-
-#postmatch{
-}
-
-#qr-code{
- text-align: center;
- color: black;
-}
-
-h1{
- font-family: Roboto;
- text-align: center;
- border-style: groove;
- border-color: white;
- border-width: 2px;
- font-size: 40px;
- padding: 10px;
- font-weight: 500;
-}
-
-#page2, #page4 {
- color: black;
-}
-
-h2{
- font-family: Roboto;
- text-align: center;
- font-size: 30px;
- font-weight: 400;
- font-style: italic;
-}
-
-.match-label{
- text-align: center;
-}
-
-.half-button-l{
- width: 48%;
- margin: 1vw;
- height: 100%;
- font: roboto;
-}
-
-.half-button-r{
- flex-grow: 1;
- height: 100%;
- width: 48%;
- margin: 1vw;
- font: roboto;
-}
-
-.half-button-container{
- display: flex;
-}
-
-#nextButton1, #nextButton2, #nextButton9, #nextButton10, .clearForm, .submitForm, #displayButton, #copyButton {
- font-family: Roboto;
- font-weight: 500;
- padding: 10px;
- background-color: rgb(151, 199, 240);
- text-decoration: none;
- display: inline-block;
-}
-
-#nextButton3, #nextButton4, #nextButton5, #nextButton6, #nextButton7, #nextButton8 {
- font-family: Roboto;
- font-weight: 500;
- background-color: black;
- padding: 10px;
- color: white;
-}
-
-.undoButton, .flipButton {
- font-family: Roboto;
- font-weight: 500;
- background-color: black;
- padding: 10px;
- color: white;
- margin-left: 20px;
-}
-
-#prevButton7, #prevButton8, #prevButton9{
- font-family: Roboto;
- font-weight: 500;
- background-color: rgb(151, 199, 240);
- padding: 10px;
-}
-
-#prevButton1, #prevButton2, #prevButton3, #prevButton4, #prevButton5, #prevButton6{
- font-family: Roboto;
- font-weight: 500;
- background-color: black;
- padding: 10px;
- color: white;
-
-}
-
-#prematch_table{
- font: Roboto;
- font-weight: 500;
- text-align: center;
-}
-
-
-h1 .odd {
- font-family: Roboto;
- font-weight: 500;
- text-align: center;
- color: #0AB4EC;
-}
-
-h1 .even {
- font-family: Roboto;
- font-weight: 500;
- text-align: center;
- color: #42B333;
-}
-
-table {
- border: 1px solid black;
- width: 100%;
-}
-
-td {
- padding: 10px 4px;
-}
-
-input {
- font-size: 16px;
- font: roboto;
- color: black;
- background-color: white;
-}
-
-.title {
- width: "50%";
- text-align: right;
-}
-
-.field {
- width: "50%";
- text-align: left;
-}
-
-#qr-table {
- text-align: center;
- color: black;
- background-color: white;
-}
-
-#data {
- font-family:'Lucida Console', 'monospace';
- text-align: justify;
-}
+/* @font-face {
+ font-family: 'Roboto';
+ src: url('./fonts/Roboto-Regular.ttf') format('truetype');
+ font-weight: 400;
+ font-style: normal;
+ font-display: swap;
+}
+*/
+
+html, body{
+ height: 100%;
+ width: 100%;
+ margin: 0;
+ padding: 0;
+ color: white;
+ background-color: black;
+ font-family: Roboto, sans-serif;
+}
+
+#prematch_table, #postmatch_table {
+
+ font-family : Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif !important;
+ text-align: center;
+ color: white;
+ font-size: 13px;
+ font-style: bold;
+ font-weight: 700;
+ letter-spacing: 1px;
+ border-width: 4px;
+ border-color: white;
+}
+
+#auton_table, #endgame_table, #teleop_table {
+ font-family : Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif !important;
+ color: black;
+ font-size: 13px;
+ font-style: bold;
+ font-weight: 700;
+ letter-spacing: 1px;
+ border-width: 4px;
+ border-color: black;
+}
+
+
+.button {
+ font-family: Roboto;
+ font-size: 16px;
+ font-weight: 500;
+}
+
+.counter-button {
+ width: 40px;
+ height: 40px;
+ font-size: 14px;
+ padding: 5px;
+ border-radius: 4px;
+ background-color: #f6faff;
+ color: black;
+ border: black;
+ border-width: 1px;
+ border-style: solid;
+ cursor: pointer;
+
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+/* hide the actual radio input */
+.radio-btn input[type="radio"] {
+ display: none;
+}
+
+.radio-btn span {
+ display: inline-block;
+ padding: 6px 12px; /* size of the box */
+ margin: 2px; /* spacing between buttons */
+ border: 1px solid #444; /* box border */
+ border-radius: 4px; /* rounded corners */
+ cursor: pointer;
+ user-select: none;
+ text-align: center;
+}
+
+/* highlight when selected */
+.radio-btn input[type="radio"]:checked + span {
+ background-color: rgb(239, 239, 236);
+ color: black;
+}
+
+.radio-btn input[type="radio"][value^="r"] + span {
+ border-color: #d9534f;
+ border-style: solid;
+ border-width: 1px;
+ color: white;
+}
+
+.radio-btn input[type="radio"][value^="b"] + span {
+ border-color: #0275d8;
+ border-style: solid;
+ border-width: 1px;
+ color: white;
+}
+
+.radio-btn input[type="radio"][value^="r"]:checked + span {
+ background-color: #d9534f;
+ border-color: #d9534f;
+ border-style: solid;
+ border-width: 5px;
+ color: white;
+}
+
+.radio-btn input[type="radio"][value^="b"]:checked + span {
+ background-color: #0275d8;
+ border-color: #0275d8;
+ border-style: solid;
+ border-width: 5px;
+ color: white;
+}
+
+/* Style the label to look like a button */
+.check-btn {
+ display: inline-block;
+ padding: 15px 15px;
+ margin: 5px;
+ border-radius: 4px;
+ border-color: black;
+ border-style: solid;
+ border-width: 1px;
+ cursor: pointer;
+ color: white;
+ text-align: center;
+ user-select: none; /* Disable text selection */
+}
+
+@media (max-width: 768px) {
+ .check-btn {
+ padding: 4vw 6vw; /* Adjust padding for mobile */
+ }
+}
+
+/* Ensure no default checkbox styles */
+input[type="checkbox"] {
+ display: none; /* Hide the checkbox, it will be visually represented by the label */
+}
+
+#autonHeader, #endgameHeader, #teleopHeader1, #teleopHeader2{
+ color: black;
+ border-color: black;
+ border-width: 4px;
+}
+
+#postmatchHeader{
+ border-width: 4px;
+ border-color: white;
+}
+
+#prematchHeader1, #prematchHeader2{
+ color: white;
+ border-color: white;
+ border-width: 4px;
+}
+
+#qrHeader1, #qrHeader2 {
+ color: black;
+}
+
+.field-image {
+ margin: 2vw;
+}
+
+.field-image-src {
+ width: 225px;
+ max-width: 500px;
+ height: auto;
+ background-position: center;
+ border: 1px solid;
+}
+
+.main-panel{
+ display: none;
+ height: 100%;
+ min-height: 100%;
+ width: 100vw;
+ position: absolute;
+ left: 0;
+ top: 0;
+ color: white;
+ background-color: black;
+ font-family : Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif !important;
+ text-align: justify;
+}
+
+#prematch{
+ display: table;
+}
+
+#auton{
+}
+
+#teleop{
+}
+
+#endgame{
+}
+
+#postmatch{
+}
+
+#qr-code{
+ text-align: center;
+ color: black;
+}
+
+h1{
+ font-family: Roboto;
+ text-align: center;
+ border-style: groove;
+ border-color: white;
+ border-width: 2px;
+ font-size: 40px;
+ padding: 10px;
+ font-weight: 500;
+}
+
+#page2, #page4 {
+ color: black;
+}
+
+h2{
+ font-family: Roboto;
+ text-align: center;
+ font-size: 30px;
+ font-weight: 400;
+ font-style: italic;
+}
+
+.match-label{
+ text-align: center;
+}
+
+.half-button-l{
+ width: 48%;
+ margin: 1vw;
+ height: 100%;
+ font: roboto;
+}
+
+.half-button-r{
+ flex-grow: 1;
+ height: 100%;
+ width: 48%;
+ margin: 1vw;
+ font: roboto;
+}
+
+.half-button-container{
+ display: flex;
+}
+
+#nextButton1, #nextButton2, #nextButton9, #nextButton10, .clearForm, .submitForm, #displayButton, #copyButton {
+ font-family: Roboto;
+ font-weight: 500;
+ padding: 10px;
+ background-color: rgb(151, 199, 240);
+ text-decoration: none;
+ display: inline-block;
+}
+
+#nextButton3, #nextButton4, #nextButton5, #nextButton6, #nextButton7, #nextButton8 {
+ font-family: Roboto;
+ font-weight: 500;
+ background-color: black;
+ padding: 10px;
+ color: white;
+}
+
+.undoButton, .flipButton {
+ font-family: Roboto;
+ font-weight: 500;
+ background-color: black;
+ padding: 10px;
+ color: white;
+ margin-left: 20px;
+}
+
+#prevButton7, #prevButton8, #prevButton9{
+ font-family: Roboto;
+ font-weight: 500;
+ background-color: rgb(151, 199, 240);
+ padding: 10px;
+}
+
+#prevButton1, #prevButton2, #prevButton3, #prevButton4, #prevButton5, #prevButton6{
+ font-family: Roboto;
+ font-weight: 500;
+ background-color: black;
+ padding: 10px;
+ color: white;
+
+}
+
+#prematch_table{
+ font: Roboto;
+ font-weight: 500;
+ text-align: center;
+}
+
+
+h1 .odd {
+ font-family: Roboto;
+ font-weight: 500;
+ text-align: center;
+ color: #C0362C;
+}
+
+h1 .even {
+ font-family: Roboto;
+ font-weight: 500;
+ text-align: center;
+ color: #C0362C;
+}
+
+table {
+ border: 1px solid black;
+ width: 100%;
+}
+
+td {
+ padding: 10px 4px;
+}
+
+input {
+ font-size: 16px;
+ font: roboto;
+ color: black;
+ background-color: white;
+}
+
+.title {
+ width: "50%";
+ text-align: right;
+}
+
+.field {
+ width: "50%";
+ text-align: left;
+}
+
+#qr-table {
+ text-align: center;
+ color: black;
+ background-color: white;
+}
+
+#data {
+ font-family:'Lucida Console', 'monospace';
+ text-align: justify;
+}
+
+/* ── Qualitative scouting additions ─────────────────────────────────────── */
+
+.qual-status-msg {
+ font-style: italic;
+ font-size: 0.9em;
+ text-align: center;
+ padding: 4px 0 8px;
+ min-height: 1.4em;
+}
+
+.qual-hint {
+ font-size: 0.85em;
+ font-style: italic;
+ color: #ccc;
+ margin: 0 0 10px;
+}
+
+.qual-divider {
+ border: none;
+ border-top: 1px solid #666;
+ margin: 6px 0;
+}
+
+.qual-team-header {
+ font-weight: bold;
+ font-size: 1.0em;
+ padding: 8px 4px 4px;
+}
+
+.qual-red { color: #ff6666; }
+.qual-blue { color: #6699ff; }
+
+.qual-label {
+ font-weight: bold;
+ font-size: 0.9em;
+}
+
+.qual-radio {
+ font-size: 0.88em;
+ margin-right: 6px;
+ white-space: nowrap;
+}
+
+.qual-radio input[type="radio"] {
+ display: inline; /* override the hidden rule for checkboxes */
+ width: auto;
+}
+
+.qual-textarea {
+ width: 98%;
+ font-size: 0.9em;
+ padding: 4px;
+ box-sizing: border-box;
+ background-color: white;
+ color: black;
+}
+
+/* Tier row */
+.qual-tier-row {
+ display: grid;
+ grid-template-columns: 140px repeat(5, 1fr);
+ align-items: center;
+ gap: 4px;
+ margin-bottom: 6px;
+}
+
+.qual-tier-team {
+ font-weight: bold;
+ font-size: 0.9em;
+ min-width: 130px;
+}
+
+.qual-tier-pills {
+ display: contents; /* lets each pill sit directly in the grid */
+}
+
+.qual-tier-pill {
+ display: block;
+ text-align: center;
+ padding: 4px 9px;
+ border: 1px solid #888;
+ border-radius: 4px;
+ font-size: 0.8em;
+ cursor: pointer;
+ user-select: none;
+ color: white;
+}
+
+.qual-tier-pill input[type="radio"] {
+ display: none;
+}
+
+.qual-tier-pill:has(input:checked) {
+ background-color: rgb(151, 199, 240);
+ color: black;
+ border-color: white;
+}
+
+/* Within-tier drag ranking */
+.qual-rank-group {
+ margin: 14px 0 6px;
+}
+
+.qual-rank-group-label {
+ font-size: 0.82em;
+ font-style: italic;
+ color: #bbb;
+ margin-bottom: 4px;
+}
+
+.qual-rank-list {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+
+.qual-rank-item {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ border: 1px solid #888;
+ border-radius: 4px;
+ padding: 6px 10px;
+ margin-bottom: 5px;
+ cursor: grab;
+ font-size: 0.92em;
+ background-color: rgba(255,255,255,0.08);
+ touch-action: none;
+}
+
+.qual-rank-item:active { cursor: grabbing; }
+
+.qual-rank-item.qual-dragging { opacity: 0.4; }
+
+.qual-rank-item.qual-drag-over {
+ border: 1px dashed #fff;
+ background-color: rgba(255,255,255,0.22);
+}
+
+.qual-drag-handle {
+ opacity: 0.5;
+ font-size: 1.1em;
+}
+
+.qual-rank-name {
+ font-size: 0.85em;
+ opacity: 0.75;
+}
\ No newline at end of file
diff --git a/resources/js/googleSheets.js b/resources/js/googleSheets.js
index 99cdb646c..74d1ba66a 100644
--- a/resources/js/googleSheets.js
+++ b/resources/js/googleSheets.js
@@ -2,16 +2,19 @@ function setUpGoogleSheets() {
const scriptURL = 'https://script.google.com/macros/s/AKfycbxYpArzIdDBCy3ZZmjH2KZTErAfJWl0OsqWO_46VfBQaPsyqC0CuW3rL8fK_Qp80zE/exec'
const form = document.querySelector('#scoutingForm')
const btn = document.querySelector('#submit')
-
-
+
+ if (!form || !btn) return;
+
form.addEventListener('submit', e => {
e.preventDefault()
- btn.disabled = false
+ btn.disabled = true
btn.innerHTML = "Sending..."
let fd = getData("tsv");
if (pitScouting) {
- fd = "PIT\t" + fd;
+ fd = "PIT\t" + fd;
+ } else if (typeof qualCollectData === 'function') {
+ fd = "QUAL\t" + qualCollectData();
}
fetch(scriptURL, {
method: "POST",
@@ -27,3 +30,5 @@ function setUpGoogleSheets() {
btn.innerHTML = "Send to Google Sheets"
})
}
+
+