diff --git a/_sass/circular-progress.scss b/_sass/circular-progress.scss index 0b92f282..2cfd2f02 100644 --- a/_sass/circular-progress.scss +++ b/_sass/circular-progress.scss @@ -1,14 +1,12 @@ .circular-button { - position: relative; - width: 70px; - height: 70px; - line-height: 70px; + align-items: center; background: none; - margin-right: 8px; - margin-bottom: 8px; display: flex; + height: 4.5rem; justify-content: center; - align-items: center; + line-height: 4.5rem; + position: relative; + width: 4.5rem; } .circular-button:after { @@ -16,7 +14,7 @@ width: 100%; height: 100%; border-radius: 50%; - border: 5px solid var(--circular-progress-border); + border: 0.3rem solid var(--circular-progress-border); position: absolute; top: 0; left: 0; @@ -43,8 +41,8 @@ width: 100%; height: 100%; background: none; - border-color: #0d6efd; - border-width: 5px; + border-color: var(--bs-primary); + border-width: 0.3rem; border-style: solid; position: absolute; top: 0; @@ -52,23 +50,23 @@ .circular-button .progress-left .progress-bar { left: 100%; - border-top-right-radius: 35px; - border-bottom-right-radius: 35px; + border-top-right-radius: 2.25rem; + border-bottom-right-radius: 2.25rem; border-left: 0; transform-origin: center left; } .circular-button .progress-right .progress-bar { left: -100%; - border-top-left-radius: 35px; - border-bottom-left-radius: 35px; + border-top-left-radius: 2.25rem; + border-bottom-left-radius: 2.25rem; border-right: 0; transform-origin: center right; } .circular-button .button-content { - width: 80%; - height: 80%; + width: 82%; + height: 82%; border-radius: 50%; background: var(--circular-progress-bg); display: flex; @@ -76,25 +74,31 @@ justify-content: center; align-items: center; position: absolute; - top: 10%; - left: 10%; + top: 9%; + left: 9%; z-index: 2; text-align: center; } .circular-button .button-name { + font-size: 0.72rem; font-weight: bold; margin-bottom: 2px; line-height: 1.2; } .circular-button .button-value { - font-size: 0.85rem; - line-height: 0.5; + color: var(--footer-text-col); + font-family: var(--bs-font-monospace); + font-size: 0.68rem; + font-variant-numeric: tabular-nums; + line-height: 1; + margin-top: 0.2rem; } .circular-button.active .button-content { - box-shadow: 0 0 10px 3px rgba(13, 110, 253, 0.5); + background-color: var(--card-primary-bg); + box-shadow: 0 0 0.8rem 0.2rem rgba(var(--bs-primary-rgb), 0.35); } .circular-button .button-image-container { @@ -106,8 +110,8 @@ } .circular-button .button-image { - max-width: 2.5rem; - max-height: 2.5rem; + max-width: 2.15rem; + max-height: 2.15rem; width: auto; height: auto; } diff --git a/_sass/styles.scss b/_sass/styles.scss index 4910f2f2..001e7399 100644 --- a/_sass/styles.scss +++ b/_sass/styles.scss @@ -219,24 +219,225 @@ table tr:nth-child(2n) { color: var(--text-col); } -/* Gamepad info cards */ -.gamepad-info-card { +/* Gamepad tester */ +.gamepad-tester .container { + max-width: 82rem; +} + +.gamepad-tester-header { + max-width: 52rem; +} + +.gamepad-kicker, +.gamepad-panel-eyebrow { + color: var(--link-col); + font-size: 0.72rem; + font-weight: 700; + letter-spacing: 0.09em; + text-transform: uppercase; +} + +.gamepad-kicker { + align-items: center; + display: inline-flex; + gap: 0.45rem; +} + +.gamepad-status { + align-items: center; background-color: var(--navbar-col); + border: 1px solid var(--navbar-border-col); + border-left-width: 0.3rem; + border-radius: 0.75rem; color: var(--text-col); + display: flex; + gap: 0.75rem; + margin-inline: auto; + max-width: 46rem; + padding: 0.8rem 1rem; + text-align: left; +} + +.gamepad-status.alert-warning { + border-left-color: var(--bs-warning); +} + +.gamepad-status.alert-success { + border-left-color: var(--bs-success); +} + +.gamepad-status.alert-danger { + border-left-color: var(--bs-danger); +} + +.gamepad-status-indicator { + background-color: var(--bs-warning); + border-radius: 50%; + box-shadow: 0 0 0 0.3rem rgba(var(--bs-warning-rgb), 0.15); + flex: 0 0 auto; + height: 0.65rem; + width: 0.65rem; } -.gamepad-info-table { +.gamepad-status.alert-success .gamepad-status-indicator { + animation: gamepad-status-pulse 2s ease-out infinite; + background-color: var(--bs-success); + box-shadow: 0 0 0 0.3rem rgba(var(--bs-success-rgb), 0.15); +} + +.gamepad-status.alert-danger .gamepad-status-indicator { + background-color: var(--bs-danger); + box-shadow: 0 0 0 0.3rem rgba(var(--bs-danger-rgb), 0.15); +} + +@keyframes gamepad-status-pulse { + 0%, 100% { + box-shadow: 0 0 0 0.25rem rgba(var(--bs-success-rgb), 0.12); + } + 50% { + box-shadow: 0 0 0 0.5rem rgba(var(--bs-success-rgb), 0); + } +} + +.gamepad-privacy-note { + font-size: 0.78rem; +} + +.gamepad-selector-panel, +.gamepad-panel { + background-color: var(--navbar-col); + border: 1px solid var(--navbar-border-col); + border-radius: 1rem; + box-shadow: 0 0.35rem 1.25rem rgba(0, 0, 0, 0.06); color: var(--text-col); + overflow: hidden; } -.gamepad-info-table th, -.gamepad-info-table td { - border-color: var(--footer-col); +[data-bs-theme="dark"] .gamepad-selector-panel, +[data-bs-theme="dark"] .gamepad-panel { + box-shadow: 0 0.35rem 1.25rem rgba(0, 0, 0, 0.2); } -.gamepad-raw-data { +.gamepad-selector-heading, +.gamepad-panel-header { + align-items: center; + border-bottom: 1px solid var(--navbar-border-col); + display: flex; + flex-wrap: wrap; + gap: 0.75rem 1rem; + justify-content: space-between; + padding: 1rem 1.25rem; +} + +.gamepad-selector-grid { + display: grid; + gap: 0.75rem; + grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr)); + padding: 1rem; +} + +.gamepad-selector-card { + align-items: center; background-color: var(--footer-col); + border: 1px solid var(--navbar-border-col); + border-radius: 0.75rem; color: var(--text-col); + cursor: pointer; + display: grid; + font: inherit; + gap: 0.75rem; + grid-template-columns: auto minmax(0, 1fr) auto; + min-width: 0; + padding: 0.8rem; + text-align: left; + transition: background-color 150ms ease, border-color 150ms ease, box-shadow 150ms ease; +} + +.gamepad-selector-card:hover { + border-color: rgba(var(--bs-primary-rgb), 0.65); +} + +.gamepad-selector-card:focus-visible { + box-shadow: 0 0 0 0.25rem rgba(var(--bs-primary-rgb), 0.25); + outline: 0; +} + +.gamepad-selector-card[aria-pressed="true"] { + background-color: var(--card-primary-bg); + border-color: var(--bs-primary); + box-shadow: inset 0 0 0 1px var(--bs-primary); +} + +.gamepad-selector-icon { + align-items: center; + background-color: var(--navbar-col); + border: 1px solid var(--navbar-border-col); + border-radius: 0.65rem; + display: flex; + font-size: 1.2rem; + height: 2.75rem; + justify-content: center; + width: 2.75rem; +} + +.gamepad-selector-card[aria-pressed="true"] .gamepad-selector-icon { + background-color: rgba(var(--bs-primary-rgb), 0.12); + border-color: rgba(var(--bs-primary-rgb), 0.45); + color: var(--bs-primary); +} + +.gamepad-selector-name { + font-size: 0.9rem; + font-weight: 700; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.gamepad-selector-meta { + color: var(--footer-text-col); + display: block; + font-size: 0.72rem; + margin-top: 0.15rem; +} + +.gamepad-selector-active { + background-color: var(--bs-primary); + border-radius: 999px; + color: #fff; + font-size: 0.65rem; + font-weight: 700; + padding: 0.2rem 0.45rem; + text-transform: uppercase; +} + +.gamepad-panel-body { + padding: 1.25rem; +} + +.gamepad-live-badge { + align-items: center; + background-color: rgba(var(--bs-success-rgb), 0.12); + border: 1px solid rgba(var(--bs-success-rgb), 0.35); + border-radius: 999px; + color: var(--bs-success); + display: inline-flex; + font-size: 0.72rem; + font-weight: 700; + gap: 0.4rem; + padding: 0.25rem 0.6rem; + text-transform: uppercase; +} + +.gamepad-live-badge > span { + background-color: currentColor; + border-radius: 50%; + height: 0.45rem; + width: 0.45rem; +} + +.gamepad-visual-body { + background-image: radial-gradient(circle at center, rgba(var(--bs-primary-rgb), 0.08), transparent 58%); } /* Controller visualization */ @@ -276,7 +477,7 @@ table tr:nth-child(2n) { --gamepad-trigger-value: 0%; align-items: center; background-color: var(--footer-col); - border: 2px solid var(--navbar-border-col); + border: 1px solid var(--navbar-border-col); border-radius: 0.75rem; display: flex; flex-direction: column; @@ -348,7 +549,524 @@ table tr:nth-child(2n) { transition: transform 50ms linear; } +.gamepad-button-grid { + display: grid; + gap: 0.9rem 0.6rem; + grid-template-columns: repeat(auto-fit, minmax(4.75rem, 1fr)); + justify-items: center; +} + +.gamepad-axis-grid { + display: grid; + gap: 1rem; + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.gamepad-axis { + background-color: var(--footer-col); + border: 1px solid var(--navbar-border-col); + border-radius: 0.75rem; + padding: 0.85rem; +} + +.gamepad-axis-header { + align-items: center; + display: flex; + font-size: 0.82rem; + font-weight: 600; + gap: 0.75rem; + justify-content: space-between; + margin-bottom: 0.65rem; +} + +.gamepad-axis-value, +.gamepad-range-control output { + font-family: var(--bs-font-monospace); + font-variant-numeric: tabular-nums; +} + +.gamepad-axis-value { + background-color: var(--navbar-col); + border: 1px solid var(--navbar-border-col); + border-radius: 0.4rem; + min-width: 3.75rem; + padding: 0.15rem 0.4rem; + text-align: center; +} + +.gamepad-axis-track { + background-color: var(--navbar-col); + border: 1px solid var(--navbar-border-col); + border-radius: 999px; + height: 0.65rem; + overflow: hidden; + position: relative; +} + +.gamepad-axis-center { + background-color: var(--footer-text-col); + height: 100%; + left: calc(50% - 1px); + opacity: 0.65; + position: absolute; + top: 0; + width: 2px; + z-index: 2; +} + +.gamepad-axis-fill { + background-color: var(--bs-info); + height: 100%; + left: 50%; + position: absolute; + top: 0; + transition: left 50ms linear, width 50ms linear; + width: 0; +} + +.gamepad-axis-fill.is-positive { + background-color: var(--bs-success); +} + +.gamepad-axis-fill.is-negative { + background-color: var(--bs-danger); +} + +/* Stick visualization */ +.stick-circle { + border: 2px solid var(--navbar-border-col); + border-radius: 50%; + background-color: var(--footer-col); + background-image: radial-gradient(circle, transparent 0 24%, var(--navbar-border-col) 25% 26%, transparent 27%); + height: 8rem; + overflow: hidden; + width: 8rem; +} + +.stick-position { + background-color: var(--bs-primary); + border: 0.2rem solid var(--navbar-col); + border-radius: 50%; + box-shadow: 0 0 0 0.15rem rgba(var(--bs-primary-rgb), 0.25); + height: 1.1rem; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + width: 1.1rem; + z-index: 3; +} + +.stick-line-vertical { + background-color: var(--navbar-border-col); + height: 100%; + left: 50%; + top: 0; + width: 1px; +} + +.stick-line-horizontal { + background-color: var(--navbar-border-col); + height: 1px; + left: 0; + top: 50%; + width: 100%; +} + +.gamepad-stick-label { + color: var(--footer-text-col); + font-size: 0.76rem; + font-weight: 700; + letter-spacing: 0.04em; + margin-bottom: 0.75rem; + text-transform: uppercase; +} + +.gamepad-capability { + align-items: center; + display: flex; + flex-wrap: wrap; + gap: 0.4rem; + min-height: 1.5rem; +} + +.gamepad-capability-badge, +.gamepad-capability-type { + border-radius: 999px; + font-size: 0.72rem; + padding: 0.25rem 0.55rem; +} + +.gamepad-capability-badge { + background-color: rgba(var(--bs-success-rgb), 0.12); + border: 1px solid rgba(var(--bs-success-rgb), 0.35); + color: var(--bs-success); + font-weight: 700; +} + +.gamepad-capability-type { + background-color: var(--footer-col); + border: 1px solid var(--navbar-border-col); + color: var(--footer-text-col); +} + +.gamepad-range-control { + margin-bottom: 1rem; +} + +.gamepad-range-control label { + align-items: center; + display: flex; + font-size: 0.82rem; + font-weight: 600; + justify-content: space-between; + margin-bottom: 0.35rem; +} + +.gamepad-range-control output, +.gamepad-range-control label > span { + color: var(--footer-text-col); + font-size: 0.75rem; +} + +.gamepad-inline-notice { + align-items: flex-start; + background-color: rgba(var(--bs-warning-rgb), 0.1); + border: 1px solid rgba(var(--bs-warning-rgb), 0.3); + border-radius: 0.75rem; + display: flex; + font-size: 0.82rem; + gap: 0.55rem; + padding: 0.8rem; +} + +.gamepad-device-details > summary { + align-items: center; + cursor: pointer; + display: flex; + gap: 1rem; + justify-content: space-between; + list-style: none; + padding: 1rem 1.25rem; +} + +.gamepad-device-details > summary::-webkit-details-marker { + display: none; +} + +.gamepad-device-details > summary:focus-visible { + box-shadow: inset 0 0 0 0.2rem rgba(var(--bs-primary-rgb), 0.25); + outline: 0; +} + +.gamepad-device-details[open] > summary { + border-bottom: 1px solid var(--navbar-border-col); +} + +.gamepad-details-action { + align-items: center; + display: inline-flex; + font-size: 0.78rem; + gap: 0.5rem; + white-space: nowrap; +} + +.gamepad-details-action i { + transition: transform 150ms ease; +} + +.gamepad-device-details[open] .gamepad-details-action i { + transform: rotate(180deg); +} + +.gamepad-details-grid { + display: grid; + gap: 0.75rem; + grid-template-columns: repeat(4, minmax(0, 1fr)); +} + +.gamepad-detail { + background-color: var(--footer-col); + border: 1px solid var(--navbar-border-col); + border-radius: 0.65rem; + min-width: 0; + padding: 0.75rem; +} + +.gamepad-detail-wide { + grid-column: span 2; +} + +.gamepad-detail dt { + color: var(--footer-text-col); + font-size: 0.68rem; + letter-spacing: 0.06em; + margin-bottom: 0.25rem; + text-transform: uppercase; +} + +.gamepad-detail dd { + font-size: 0.85rem; + font-weight: 600; + margin-bottom: 0; + overflow-wrap: anywhere; +} + +/* Fit the complete tester workspace into a typical desktop viewport. */ +@media (min-width: 1200px) { + .gamepad-tester.has-gamepad { + padding-bottom: 1.25rem !important; + padding-top: 1.25rem !important; + } + + .gamepad-tester.has-gamepad .gamepad-tester-header { + align-items: center; + display: flex; + gap: 1rem; + max-width: none; + text-align: left !important; + } + + .gamepad-tester.has-gamepad .gamepad-kicker, + .gamepad-tester.has-gamepad .gamepad-tester-header > .lead, + .gamepad-tester.has-gamepad .gamepad-privacy-note { + display: none; + } + + .gamepad-tester.has-gamepad .gamepad-tester-header > h2 { + flex: 0 0 auto; + font-size: 1.65rem; + margin-bottom: 0 !important; + } + + .gamepad-tester.has-gamepad .gamepad-status { + flex: 1 1 auto; + margin: 0 !important; + max-width: none; + padding: 0.65rem 0.85rem; + } + + .gamepad-tester.has-gamepad .gamepad-selector-panel { + display: grid; + grid-template-columns: 13rem minmax(0, 1fr); + margin-top: 0.85rem !important; + } + + .gamepad-tester.has-gamepad .gamepad-selector-heading { + border-bottom: 0; + border-right: 1px solid var(--navbar-border-col); + padding: 0.65rem 0.85rem; + } + + .gamepad-tester.has-gamepad .gamepad-selector-heading > .small { + display: none; + } + + .gamepad-tester.has-gamepad .gamepad-selector-grid { + gap: 0.5rem; + grid-template-columns: repeat(auto-fit, minmax(13rem, 18rem)); + padding: 0.5rem; + } + + .gamepad-tester.has-gamepad .gamepad-selector-card { + gap: 0.55rem; + padding: 0.45rem 0.6rem; + } + + .gamepad-tester.has-gamepad .gamepad-selector-icon { + border-radius: 0.5rem; + font-size: 1rem; + height: 2.25rem; + width: 2.25rem; + } + + .gamepad-tester.has-gamepad #gamepad-info { + display: grid; + gap: 0.75rem; + grid-template-columns: repeat(12, minmax(0, 1fr)); + margin: 0.75rem 0 0 !important; + } + + .gamepad-tester.has-gamepad #gamepad-info > [class*="col-"] { + padding: 0; + width: auto; + } + + .gamepad-tester.has-gamepad #gamepad-info > :nth-child(1) { + grid-column: 1 / 8; + grid-row: 1; + } + + .gamepad-tester.has-gamepad #gamepad-info > :nth-child(2) { + grid-column: 8 / 13; + grid-row: 1; + } + + .gamepad-tester.has-gamepad #gamepad-info > :nth-child(3) { + grid-column: 7 / 10; + grid-row: 2; + } + + .gamepad-tester.has-gamepad #gamepad-info > :nth-child(4) { + grid-column: 1 / 7; + grid-row: 2; + } + + .gamepad-tester.has-gamepad #gamepad-info > :nth-child(5) { + grid-column: 10 / 13; + grid-row: 2; + } + + .gamepad-tester.has-gamepad #gamepad-info > :nth-child(6) { + grid-column: 1 / 13; + grid-row: 3; + } + + .gamepad-tester.has-gamepad .gamepad-visual-panel { + height: 100%; + } + + .gamepad-tester.has-gamepad .gamepad-panel-header { + gap: 0.5rem; + padding: 0.55rem 0.75rem; + } + + .gamepad-tester.has-gamepad .gamepad-panel-header h3 { + font-size: 0.95rem; + } + + .gamepad-tester.has-gamepad .gamepad-panel-eyebrow { + font-size: 0.6rem; + } + + .gamepad-tester.has-gamepad .gamepad-panel-body { + padding: 0.7rem; + } + + .gamepad-tester.has-gamepad .gamepad-visual { + min-height: 12rem; + } + + .gamepad-tester.has-gamepad .gamepad-visual-svg { + max-height: 12.5rem; + } + + .gamepad-tester.has-gamepad .gamepad-trigger-visual { + padding: 0.3rem; + top: 1.25rem; + width: 4.5rem; + } + + .gamepad-tester.has-gamepad .gamepad-trigger-visual:first-child { + left: max(calc(50% - 22rem), 0.25rem); + } + + .gamepad-tester.has-gamepad .gamepad-trigger-visual:last-child { + right: max(calc(50% - 22rem), 0.25rem); + } + + .gamepad-tester.has-gamepad .gamepad-trigger-image { + max-height: 2rem; + width: 3rem; + } + + .gamepad-tester.has-gamepad .gamepad-button-grid { + gap: 0.4rem; + grid-template-columns: repeat(6, minmax(0, 1fr)); + } + + .gamepad-tester.has-gamepad .gamepad-button-grid .circular-button { + height: 3.5rem; + line-height: 3.5rem; + width: 3.5rem; + } + + .gamepad-tester.has-gamepad .gamepad-button-grid .button-image { + max-height: 1.65rem; + max-width: 1.65rem; + } + + .gamepad-tester.has-gamepad .gamepad-button-grid .button-name { + font-size: 0.62rem; + } + + .gamepad-tester.has-gamepad .gamepad-button-grid .button-value { + font-size: 0.6rem; + margin-top: 0.1rem; + } + + .gamepad-tester.has-gamepad .gamepad-axis-grid { + gap: 0.5rem; + } + + .gamepad-tester.has-gamepad .gamepad-axis { + padding: 0.5rem; + } + + .gamepad-tester.has-gamepad .gamepad-axis-header { + margin-bottom: 0.35rem; + } + + .gamepad-tester.has-gamepad .stick-circle { + height: 5.75rem; + width: 5.75rem; + } + + .gamepad-tester.has-gamepad .gamepad-stick-label { + font-size: 0.65rem; + margin-bottom: 0.4rem; + } + + .gamepad-tester.has-gamepad .gamepad-capability { + margin-bottom: 0.5rem !important; + } + + .gamepad-tester.has-gamepad .gamepad-range-control { + margin-bottom: 0.45rem; + } + + .gamepad-tester.has-gamepad .gamepad-range-control label { + font-size: 0.75rem; + margin-bottom: 0.1rem; + } + + .gamepad-tester.has-gamepad .form-range { + height: 1.15rem; + } + + .gamepad-tester.has-gamepad #vibration-buttons .btn { + font-size: 0.75rem; + padding: 0.3rem 0.45rem; + } + + .gamepad-tester.has-gamepad .gamepad-device-details > summary { + padding: 0.55rem 0.75rem; + } + + .gamepad-tester.has-gamepad .gamepad-device-details > summary .h5 { + font-size: 0.95rem; + } +} + +@media (max-width: 767.98px) { + .gamepad-axis-grid, + .gamepad-details-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + @media (max-width: 575.98px) { + .gamepad-selector-heading, + .gamepad-panel-header, + .gamepad-panel-body, + .gamepad-device-details > summary { + padding: 0.9rem; + } + + .gamepad-selector-heading > .small, + .gamepad-panel-header > .small { + width: 100%; + } + .gamepad-visual { display: grid; gap: 0.35rem; @@ -374,37 +1092,20 @@ table tr:nth-child(2n) { .gamepad-trigger-value { font-size: 0.65rem; } -} - -/* Stick visualization */ -.stick-circle { - width: 120px; - height: 120px; - border: 2px solid var(--navbar-border-col); - border-radius: 50%; -} -.stick-position { - width: 16px; - height: 16px; - border-radius: 50%; - transform: translate(-50%, -50%); -} + .gamepad-axis-grid, + .gamepad-details-grid { + grid-template-columns: minmax(0, 1fr); + } -.stick-line-vertical { - width: 2px; - height: 100%; - background-color: var(--navbar-border-col); - left: 50%; - top: 0; -} + .gamepad-detail-wide { + grid-column: auto; + } -.stick-line-horizontal { - width: 100%; - height: 2px; - background-color: var(--navbar-border-col); - left: 0; - top: 50%; + .stick-circle { + height: 6.75rem; + width: 6.75rem; + } } /* Project card styling */ @@ -464,32 +1165,6 @@ table tr:nth-child(2n) { opacity: 1 !important; } -/* gamepad selector cards */ -.gamepad-selector-card { - transition: all 0.2s ease-in-out; - user-select: none; - background-color: var(--navbar-col); - color: var(--text-col); -} - -.gamepad-selector-card.border-secondary { - background-color: var(--footer-col); -} - -.gamepad-selector-card.border-primary { - background-color: var(--card-primary-bg); - box-shadow: var(--card-primary-shadow); -} - -.gamepad-selector-card:hover { - transform: translateY(-2px); - box-shadow: var(--card-hover-shadow); -} - -.gamepad-selector-card.border-primary:hover { - box-shadow: var(--card-primary-hover-shadow); -} - /* Override Bootstrap's text-muted to use theme colors */ .text-muted { color: var(--footer-text-col) !important; diff --git a/assets/css/gamepad-tester.css b/assets/css/gamepad-tester.css new file mode 100644 index 00000000..5a4b3d35 --- /dev/null +++ b/assets/css/gamepad-tester.css @@ -0,0 +1,9 @@ +.intro-header { + margin-bottom: 0; +} + +@media (min-width: 1200px) { + .intro-header { + margin-top: 5.6875rem; + } +} diff --git a/assets/js/gamepad-tester.js b/assets/js/gamepad-tester.js index 044b0232..634430e0 100644 --- a/assets/js/gamepad-tester.js +++ b/assets/js/gamepad-tester.js @@ -13,10 +13,12 @@ document.addEventListener('DOMContentLoaded', function() { let gamepads = {}; let activeGamepadIndex = null; let animationFrameId = null; - let gamepadSelector = document.getElementById('gamepad-selector'); - let gamepadSelectorContainer = document.getElementById('gamepad-selector-container'); - let gamepadInfoSection = document.getElementById('gamepad-info'); - let gamepadStatus = document.getElementById('gamepad-status'); + const gamepadSelector = document.getElementById('gamepad-selector'); + const gamepadSelectorContainer = document.getElementById('gamepad-selector-container'); + const gamepadInfoSection = document.getElementById('gamepad-info'); + const gamepadTester = document.getElementById('GamepadTester'); + const gamepadStatus = document.getElementById('gamepad-status'); + const gamepadStatusMessage = document.getElementById('gamepad-status-message'); let gamepadVisualButtons = new Map(); let gamepadVisualSticks = []; let gamepadVisualTriggers = new Map(); @@ -105,9 +107,7 @@ document.addEventListener('DOMContentLoaded', function() { // Check if the Gamepad API is supported if (!gamepadHelper.isSupported()) { - gamepadStatus.textContent = 'Gamepad API not supported in this browser'; - gamepadStatus.classList.remove('alert-warning'); - gamepadStatus.classList.add('alert-danger'); + updateStatus('The Gamepad API is not supported in this browser.', 'danger'); return; } @@ -186,65 +186,40 @@ document.addEventListener('DOMContentLoaded', function() { stopVibration(); }); - document.getElementById('vibration-weak').addEventListener('input', function() { - document.getElementById('weak-value').textContent = this.value; - }); - - document.getElementById('vibration-strong').addEventListener('input', function() { - document.getElementById('strong-value').textContent = this.value; - }); - // Update gamepad selector buttons function updateGamepadSelector() { gamepadSelector.innerHTML = ''; const gamepadIndices = Object.keys(gamepads); + const hasGamepads = gamepadIndices.length > 0; + gamepadTester.classList.toggle('has-gamepad', hasGamepads); - if (gamepadIndices.length > 0) { - gamepadSelectorContainer.style.display = 'block'; - gamepadInfoSection.style.display = 'flex'; + if (hasGamepads) { + gamepadSelectorContainer.style.removeProperty('display'); + gamepadInfoSection.style.removeProperty('display'); gamepadIndices.forEach(index => { - const card = document.createElement('div'); - card.className = 'card gamepad-selector-card'; + const card = document.createElement('button'); + card.className = 'gamepad-selector-card'; card.dataset.index = index; - card.style.cursor = 'pointer'; - card.style.minWidth = '200px'; - card.style.maxWidth = '300px'; - card.style.borderWidth = '2px'; // Always use 2px border + card.type = 'button'; const isActive = activeGamepadIndex !== null && Number.parseInt(index) === activeGamepadIndex; - - // Mark active gamepad card - if (isActive) { - card.classList.add('border-primary'); - } else { - card.classList.add('border-secondary'); - } - - const cardBody = document.createElement('div'); - cardBody.className = 'card-body p-2'; + card.setAttribute('aria-pressed', isActive.toString()); const gamepadInfo = gamepads[index]; const typeInfo = gamepadHelper.getGamepadInfo(gamepadInfo.id); - - cardBody.innerHTML = ` -
-
- -
-
-
${typeInfo.name}
-
Index: ${index}
-
${gamepadInfo.buttons.length} buttons, ${gamepadInfo.axes.length} axes
-
-
- ${isActive ? 'Active' : ''} -
-
+ card.setAttribute('aria-label', `${isActive ? 'Active controller' : 'Select controller'}: ${typeInfo.name}`); + + card.innerHTML = ` + + + ${typeInfo.name} + Index ${index} · ${gamepadInfo.buttons.length} buttons · ${gamepadInfo.axes.length} axes + + ${isActive ? 'Active' : ''} `; - card.appendChild(cardBody); gamepadSelector.appendChild(card); }); @@ -438,6 +413,11 @@ document.addEventListener('DOMContentLoaded', function() { const buttonDiv = document.createElement('div'); buttonDiv.className = 'circular-button'; buttonDiv.id = `button-${i}`; + buttonDiv.setAttribute('aria-label', `${buttonName} button value`); + buttonDiv.setAttribute('aria-valuemax', '1'); + buttonDiv.setAttribute('aria-valuemin', '0'); + buttonDiv.setAttribute('aria-valuenow', '0'); + buttonDiv.setAttribute('role', 'progressbar'); // Create the progress elements const progressLeft = document.createElement('span'); @@ -489,35 +469,33 @@ document.addEventListener('DOMContentLoaded', function() { const controllerType = gamepadHelper.detectControllerType(gamepad.id); for (let i = 0; i < gamepad.axes.length; i++) { - const colDiv = document.createElement('div'); - colDiv.className = 'col-md-6 col-lg-3 mb-3'; + const axis = document.createElement('div'); + axis.className = 'gamepad-axis'; const axisName = gamepadHelper.getAxisName(controllerType, i); - colDiv.innerHTML = ` -
${axisName}: 0.00
-
-
+ axis.innerHTML = ` +
+ ${axisName} + 0.00 +
+
+ +
`; - axesContainer.appendChild(colDiv); + axesContainer.appendChild(axis); } } // Update gamepad status message - function updateStatus(message) { - gamepadStatus.textContent = message; - - // Update classes based on if we have a gamepad - if (Object.keys(gamepads).length > 0) { - gamepadStatus.classList.remove('alert-warning', 'alert-danger'); - gamepadStatus.classList.add('alert-success'); - } else { - gamepadStatus.classList.remove('alert-success', 'alert-danger'); - gamepadStatus.classList.add('alert-warning'); - } + function updateStatus(message, status) { + const resolvedStatus = status || (Object.keys(gamepads).length > 0 ? 'success' : 'warning'); + gamepadStatusMessage.textContent = message; + gamepadStatus.classList.remove('alert-success', 'alert-warning', 'alert-danger'); + gamepadStatus.classList.add(`alert-${resolvedStatus}`); } // Update the gamepad info function to include vibration status @@ -561,7 +539,15 @@ document.addEventListener('DOMContentLoaded', function() { } // Show information about the actuator type - vibrationStatus.innerHTML = `Supported Actuator type: ${vibrationCapabilities.type}`; + const capabilityBadge = document.createElement('span'); + capabilityBadge.className = 'gamepad-capability-badge'; + capabilityBadge.textContent = 'Vibration supported'; + + const capabilityType = document.createElement('span'); + capabilityType.className = 'gamepad-capability-type'; + capabilityType.textContent = vibrationCapabilities.type; + + vibrationStatus.replaceChildren(capabilityBadge, capabilityType); vibrationButtons.classList.remove('d-none'); // Show appropriate controls based on an actuator type @@ -590,6 +576,7 @@ document.addEventListener('DOMContentLoaded', function() { // Update the value display buttonValueElement.textContent = value.toFixed(2); + buttonElement.setAttribute('aria-valuenow', value.toFixed(2)); // Calculate rotation degrees based on value (0 to 1) // For a full circle: right part goes from 0 to 180 degrees, left part from 0 to 180 degrees @@ -655,26 +642,27 @@ document.addEventListener('DOMContentLoaded', function() { const axisValue = gamepad.axes[i]; const axisValueElement = document.getElementById(`axis-value-${i}`); const axisProgressElement = document.getElementById(`axis-progress-${i}`); + const axisMeterElement = document.getElementById(`axis-meter-${i}`); - if (axisValueElement && axisProgressElement) { + if (axisValueElement && axisProgressElement && axisMeterElement) { // Display the value axisValueElement.textContent = axisValue.toFixed(2); + axisMeterElement.setAttribute('aria-valuenow', axisValue.toFixed(2)); - // Update the progress bar - // Convert -1 to 1 to 0 to 100 for the progress bar - const progressWidth = ((axisValue + 1) / 2) * 100; + // Fill away from the zero marker in the direction of travel. + const progressWidth = Math.abs(axisValue) * 50; + axisProgressElement.style.left = axisValue < 0 ? `${50 - progressWidth}%` : '50%'; axisProgressElement.style.width = `${progressWidth}%`; // Change color based on the direction if (axisValue > 0.1) { - axisProgressElement.classList.remove('bg-info', 'bg-danger'); - axisProgressElement.classList.add('bg-success'); + axisProgressElement.classList.remove('is-negative'); + axisProgressElement.classList.add('is-positive'); } else if (axisValue < -0.1) { - axisProgressElement.classList.remove('bg-info', 'bg-success'); - axisProgressElement.classList.add('bg-danger'); + axisProgressElement.classList.remove('is-positive'); + axisProgressElement.classList.add('is-negative'); } else { - axisProgressElement.classList.remove('bg-success', 'bg-danger'); - axisProgressElement.classList.add('bg-info'); + axisProgressElement.classList.remove('is-positive', 'is-negative'); } } } @@ -689,9 +677,9 @@ document.addEventListener('DOMContentLoaded', function() { const rightStick = document.getElementById('right-stick-position'); if (gamepad.axes.length >= 2 && leftStick) { - // Convert -1 to 1 values to pixel positions - const x = gamepad.axes[0] * 60; // Scale factor to fit within the circle - const y = gamepad.axes[1] * 60; + const range = (leftStick.parentElement.clientWidth / 2) - (leftStick.clientWidth / 2) - 2; + const x = gamepad.axes[0] * range; + const y = gamepad.axes[1] * range; // Center position is 50%, then offset by the calculated amounts leftStick.style.left = `calc(50% + ${x}px)`; @@ -699,8 +687,9 @@ document.addEventListener('DOMContentLoaded', function() { } if (gamepad.axes.length >= 4 && rightStick) { - const x = gamepad.axes[2] * 60; - const y = gamepad.axes[3] * 60; + const range = (rightStick.parentElement.clientWidth / 2) - (rightStick.clientWidth / 2) - 2; + const x = gamepad.axes[2] * range; + const y = gamepad.axes[3] * range; rightStick.style.left = `calc(50% + ${x}px)`; rightStick.style.top = `calc(50% + ${y}px)`; diff --git a/gamepad-tester.html b/gamepad-tester.html index de4ed9d7..f137ed11 100644 --- a/gamepad-tester.html +++ b/gamepad-tester.html @@ -4,6 +4,7 @@ full-width: true css: - /assets/css/hide-heading.css + - /assets/css/gamepad-tester.css ext-js: - href: "https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.5.0/chart.umd.min.js" sri: "sha512-Y51n9mtKTVBh3Jbx5pZSJNDDMyY+yGe77DGtBPzRlgsf/YLCh13kSZ3JmfHGzYFCmOndraf0sQgfM654b7dJ3w==" @@ -16,177 +17,197 @@ -
-
-
-
-

Gamepad Tester

-

Connect a controller to test its inputs and functionality

-
No gamepad detected. Connect a gamepad and press any button.
+
+
+
+
+ + Browser controller diagnostics
-
+

Gamepad Tester

+

Check buttons, sticks, triggers, axes, and vibration in real time.

+ + + No gamepad detected. Connect a gamepad and press any button. + +

+ + Input data stays in your browser. +

+ -
-
-
-
-
-
Vibration Test
+
+
+
+
+ Range -1.00 to 1.00 +

Axes

+
+ Centered at zero +
+
+
-
-
+
+
+ +
+
+
+
+ Haptics +

Vibration

+
+
+
+
-
-
- - -
+
+ +
-
-
- - -
+
+ +
-
-
- - -
+
+ +
-
-
- - -
+
+ +
-
- - +
+ +
-
- Vibration not supported by this gamepad or browser. +
+ + Vibration is not available for this controller or browser.
-
-
- -
-
-
-
Buttons
-
-
-
-
-
-
- -
-
-
-
Axes
-
-
-
-
-
+
-
-
-
-
Sticks
-
-
-
-
-
-
Left
-
-
-
-
-
-
-
-
-
-
Right
-
-
-
-
-
-
-
+
+
+ + + Technical information + Controller details + + Show details + +
+
+
Name
-
+
Device ID
-
+
Type
-
+
Mapping
-
+
Index
-
+
Connected
-
+
Buttons
-
+
Axes
-
-
+
-
-
-
-
Raw Data
-
-
-

-                    
-
-
+