Loading docs/.vuepress/components/bus-v2/BusMapV2.vue +4 −2 Changes for docs/.vuepress/components/bus-v2/BusMapV2.vue: 4 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -139,6 +139,7 @@ function updateVehicleMarker(record, vehicle) { record.element.setAttribute('aria-label', `${label} · ${record.element.title}`) record.label.textContent = label record.label.classList.toggle('bus-map__vehicle-label--compact', label.length > 1) record.label.classList.toggle('bus-map__vehicle-label--shuttle', String(vehicle.vehicle_type).toUpperCase() === 'SHUTTLE') record.label.style.color = vehicleTextColor(vehicle.vehicle_type) record.element.style.setProperty('--route-color', route?.color || '#2878c8') const bearing = vehicleBearing(vehicle) Loading Loading @@ -374,9 +375,10 @@ defineExpose({ refresh, refreshVehicleMarkers }) .bus-map__legend { position: absolute; z-index: 1; bottom: .75rem; left: .75rem; margin: 0; padding: .4rem .55rem; border-radius: .35rem; background: color-mix(in srgb, var(--bus-v2-bg, #fff) 90%, transparent); } .bus-map__overlap { position: absolute; z-index: 1; top: .75rem; left: 3rem; padding: .35rem .5rem; border-radius: .35rem; background: color-mix(in srgb, var(--bus-v2-bg, #fff) 90%, transparent); color: var(--bus-v2-text, #333); font-size: .8rem; } .bus-map :deep(.bus-map__vehicle-marker) { display: block; width: 1.6rem; min-width: 1.6rem; max-width: 1.6rem; height: 1.6rem; min-height: 1.6rem; max-height: 1.6rem; line-height: 0; } .bus-map :deep(.bus-map__vehicle) { box-sizing: border-box; display: block; position: relative; width: 1.6rem; min-width: 1.6rem; max-width: 1.6rem; height: 1.6rem; min-height: 1.6rem; max-height: 1.6rem; aspect-ratio: 1; border: 2px solid #fff; border-radius: 50%; padding: 1px; background: var(--route-color); cursor: pointer; } .bus-map :deep(.bus-map__vehicle-label) { position: absolute; inset: 2px; display: flex; align-items: center; justify-content: center; border-radius: 50%; background: #fff; font: 700 .9rem/1 "Microsoft YaHei UI", "Microsoft YaHei", "PingFang SC", sans-serif; } .bus-map :deep(.bus-map__vehicle) { box-sizing: border-box; display: block; position: relative; width: 1.6rem; min-width: 1.6rem; max-width: 1.6rem; height: 1.6rem; min-height: 1.6rem; max-height: 1.6rem; aspect-ratio: 1; border: 2px solid #fff; border-radius: 50%; padding: 1px; background: var(--route-color); box-shadow: 0 1px 4px #0005; cursor: pointer; } .bus-map :deep(.bus-map__vehicle-label) { position: absolute; inset: 2px; display: flex; align-items: center; justify-content: center; border-radius: 50%; background: var(--bus-v2-bus-fill); font: 700 .9rem/1 "Microsoft YaHei UI", "Microsoft YaHei", "PingFang SC", sans-serif; } .bus-map :deep(.bus-map__vehicle-label--compact) { font-size: .65rem; } .bus-map :deep(.bus-map__vehicle-label--shuttle) { background: var(--bus-v2-shuttle-fill); } .bus-map :deep(.bus-map__vehicle-arrow) { position: absolute; z-index: 2; inset: 0; pointer-events: none; transform: rotate(var(--bearing)); } .bus-map :deep(.bus-map__vehicle-arrow)::before { content: ''; position: absolute; top: -.57rem; left: 50%; transform: translateX(-50%); border-right: .44rem solid transparent; border-bottom: .62rem solid #fff; border-left: .44rem solid transparent; } .bus-map :deep(.bus-map__vehicle-arrow)::after { content: ''; position: absolute; top: -.45rem; left: 50%; transform: translateX(-50%); border-right: .32rem solid transparent; border-bottom: .5rem solid var(--route-color); border-left: .32rem solid transparent; } Loading docs/.vuepress/components/bus-v2/BusScheduleRowsV2.vue +3 −4 Changes for docs/.vuepress/components/bus-v2/BusScheduleRowsV2.vue: 3 added lines, 4 removed lines. Original line number Diff line number Diff line Loading @@ -21,13 +21,12 @@ <script setup> import { onBeforeUnmount, onMounted, ref } from 'vue' import { serviceTypeLabel, vehicleTextColor } from './core.mjs' import { serviceTypeLabel } from './core.mjs' const props = defineProps({ groups: { type: Array, default: () => [] }, language: { type: String, default: 'zh' } }) const activeOnly = ref(true) const collapsedRoutes = ref(new Set()) const portrait = ref(false) const busColor = vehicleTextColor('BUS'), shuttleColor = vehicleTextColor('SHUTTLE') let orientationQuery const text = (zh, en) => props.language === 'zh' ? zh : en const changeLabel = (item) => ({ EXTRA: text('加班', 'Extra'), CANCELLED: text('停运', 'Cancelled') })[item.change] || '' Loading @@ -42,9 +41,9 @@ onBeforeUnmount(() => { orientationQuery?.removeEventListener?.('change', update </script> <style scoped lang="scss"> .schedule-toolbar { display: flex; flex-wrap: wrap; gap: .55rem 1rem; align-items: center; justify-content: space-between; margin-bottom: .7rem; font-size: .84rem; }.schedule-legend { display: flex; flex-wrap: wrap; gap: .55rem 1rem; align-items: center; }.schedule-legend > span { display: inline-flex; align-items: center; gap: .3rem; }.schedule-filter { cursor: pointer; white-space: nowrap; }.swatch { width: .72rem; height: .72rem; border-radius: 50%; }.swatch.bus { background: v-bind(busColor); }.swatch.shuttle { background: v-bind(shuttleColor); }.schedule-list { overflow: hidden; border: 1px solid #d9e2ec; border-radius: .6rem; container-type: inline-size; }.schedule-row { display: flex; flex-wrap: wrap; border-bottom: 1px solid #e7edf4; }.schedule-row:last-child { border: 0; }.route-info { box-sizing: border-box; display: flex; min-width: 8rem; flex: 1 1 9rem; flex-direction: column; justify-content: center; gap: .2rem; padding: .75rem; border-left: .4rem solid; background: var(--c-bg-soft, #f8fafc); }.route-info small, .muted { color: #667085; }.times { box-sizing: border-box; display: flex; min-width: 8rem; flex: 999 1 8rem; flex-wrap: wrap; align-content: flex-start; gap: .45rem; padding: .75rem; }.time { --vehicle-color: v-bind(busColor); padding: .13rem .38rem; border-radius: .25rem; color: var(--vehicle-color); background: color-mix(in srgb, var(--vehicle-color) 6%, #fff); font-family: ui-monospace, SFMono-Regular, Consolas, monospace; }.time.shuttle { --vehicle-color: v-bind(shuttleColor); }.time.running { border: 1px solid var(--vehicle-color); background: color-mix(in srgb, var(--vehicle-color) 10%, #fff); }.time.next { background: var(--vehicle-color); color: #fff; font-weight: 700; }.time.past { opacity: .35; } .schedule-toolbar { display: flex; flex-wrap: wrap; gap: .55rem 1rem; align-items: center; justify-content: space-between; margin-bottom: .7rem; font-size: .84rem; }.schedule-legend { display: flex; flex-wrap: wrap; gap: .55rem 1rem; align-items: center; }.schedule-legend > span { display: inline-flex; align-items: center; gap: .3rem; }.schedule-filter { cursor: pointer; white-space: nowrap; }.swatch { width: .72rem; height: .72rem; border-radius: 50%; }.swatch.bus { background: var(--bus-v2-bus-fill); border: 1px solid var(--bus-v2-bus-text); }.swatch.shuttle { background: var(--bus-v2-shuttle-fill); border: 1px solid var(--bus-v2-shuttle-text); }.schedule-list { overflow: hidden; border: 1px solid #d9e2ec; border-radius: .6rem; container-type: inline-size; }.schedule-row { display: flex; flex-wrap: wrap; border-bottom: 1px solid #e7edf4; }.schedule-row:last-child { border: 0; }.route-info { box-sizing: border-box; display: flex; min-width: 8rem; flex: 1 1 9rem; flex-direction: column; justify-content: center; gap: .2rem; padding: .75rem; border-left: .4rem solid; background: var(--c-bg-soft, #f8fafc); }.route-info small, .muted { color: #667085; }.times { box-sizing: border-box; display: flex; min-width: 8rem; flex: 999 1 8rem; flex-wrap: wrap; align-content: flex-start; gap: .45rem; padding: .75rem; }.time { --vehicle-color: var(--bus-v2-bus-text); --vehicle-soft: var(--bus-v2-bus-soft); --vehicle-fill: var(--bus-v2-bus-fill); --vehicle-ink: #754600; padding: .13rem .38rem; border-radius: .25rem; color: var(--vehicle-color); background: var(--vehicle-soft); font-family: ui-monospace, SFMono-Regular, Consolas, monospace; }.time.shuttle { --vehicle-color: var(--bus-v2-shuttle-text); --vehicle-soft: var(--bus-v2-shuttle-soft); --vehicle-fill: var(--bus-v2-shuttle-fill); --vehicle-ink: #193e29; }.time.running { border: 1px solid var(--vehicle-color); background: var(--vehicle-soft); }.time.next { background: var(--vehicle-fill); color: var(--vehicle-ink); font-weight: 700; }.time.past { opacity: .35; } @container (max-width: 24rem) { .schedule-row { flex-direction: column; }.route-info { width: 100%; min-width: 0; flex: none; flex-direction: row; flex-wrap: wrap; align-items: baseline; justify-content: flex-start; border-left: 0; border-top: .35rem solid; padding: .5rem; }.times { width: 100%; min-width: 0; flex: none; } } .schedule-list { border-color: var(--bus-v2-border); background: var(--bus-v2-bg); color: var(--bus-v2-text); } .schedule-list { --bus-v2-bg: var(--bus-v2-schedule-bg); border-color: var(--bus-v2-border); background: var(--bus-v2-bg); color: var(--bus-v2-text); } .schedule-row { border-color: var(--bus-v2-border); } .route-info { background: var(--bus-v2-bg-alt); } .route-info.collapsible { cursor: pointer; } Loading docs/.vuepress/components/bus-v2/BusVehicleLegendV2.vue +3 −2 Changes for docs/.vuepress/components/bus-v2/BusVehicleLegendV2.vue: 3 added lines, 2 removed lines. Original line number Diff line number Diff line <template> <div class="vehicle-legend" :aria-label="language === 'zh' ? '车型图例' : 'Vehicle legend'"> <span><i v-if="textMarkers" class="vehicle-legend__marker" :style="{ color: vehicleTextColor('BUS') }" aria-hidden="true">1</i><img v-else src="/bus.png" alt="">{{ language === 'zh' ? '巴士' : 'Bus' }}</span> <span><i v-if="textMarkers" class="vehicle-legend__marker" :style="{ color: vehicleTextColor('SHUTTLE') }" aria-hidden="true">1</i><img v-else src="/sev.png" alt="">{{ language === 'zh' ? '电瓶车' : 'EV Shuttle' }}</span> <span><i v-if="textMarkers" class="vehicle-legend__marker shuttle" :style="{ color: vehicleTextColor('SHUTTLE') }" aria-hidden="true">1</i><img v-else src="/sev.png" alt="">{{ language === 'zh' ? '电瓶车' : 'EV Shuttle' }}</span> </div> </template> Loading @@ -13,5 +13,6 @@ defineProps({ language: { type: String, default: 'zh' }, textMarkers: Boolean }) <style scoped> .vehicle-legend { display: flex; flex-wrap: wrap; gap: .6rem; margin-top: .5rem; color: var(--bus-v2-muted); font-size: .82rem; }.vehicle-legend span { display: inline-flex; align-items: center; gap: .25rem; white-space: nowrap; } .vehicle-legend img { box-sizing: border-box; width: 1rem; height: 1rem; padding: 1px; border-radius: 50%; background: #fff; } .vehicle-legend__marker { display: inline-flex; align-items: center; justify-content: center; width: 1.25rem; height: 1.25rem; border-radius: 50%; background: #fff; font: 700 .75rem/1 "Microsoft YaHei UI", "Microsoft YaHei", "PingFang SC", sans-serif; } .vehicle-legend__marker { display: inline-flex; align-items: center; justify-content: center; width: 1.25rem; height: 1.25rem; border-radius: 50%; background: var(--bus-v2-bus-fill); font: 700 .75rem/1 "Microsoft YaHei UI", "Microsoft YaHei", "PingFang SC", sans-serif; } .vehicle-legend__marker.shuttle { background: var(--bus-v2-shuttle-fill); } </style> docs/.vuepress/components/bus-v2/core.mjs +2 −2 Changes for docs/.vuepress/components/bus-v2/core.mjs: 2 added lines, 2 removed lines. Original line number Diff line number Diff line const DEV_API_BASE = 'http://127.0.0.1:8000' const DEV_API_BASE = 'https://buseta.sustcra.com' const PROD_API_BASE = 'https://buseta.sustcra.com' export const resolveBusApiBase = (production = false) => production ? PROD_API_BASE : DEV_API_BASE export const isCampusRoute = (routeId) => ['line1', 'line2', 'k1', 'k2'].includes(routeId) export const vehicleRouteLabel = (routeId) => ({ line1: '1', line2: '2', k1: 'K1', k1a: 'K1', k2: 'K2', k2a: 'K2' })[String(routeId || '').toLowerCase()] || '' export const vehicleTextColor = (vehicleType) => String(vehicleType).toUpperCase() === 'SHUTTLE' ? '#7e22ce' : '#00451e' export const vehicleTextColor = (vehicleType) => String(vehicleType).toUpperCase() === 'SHUTTLE' ? '#193e29' : '#754600' export function serviceTypeLabel(serviceType, language = 'zh') { const labels = { NORMAL: ['常规服务', 'Normal service'], SHORT_TURN: ['区间车', 'Short turn'], EXPRESS: ['快线', 'Express'] } return labels[serviceType]?.[language === 'en' ? 1 : 0] || serviceType || '' Loading docs/.vuepress/components/bus-v2/core.test.mjs +3 −8 Changes for docs/.vuepress/components/bus-v2/core.test.mjs: 3 added lines, 8 removed lines. Original line number Diff line number Diff line import assert from 'node:assert/strict' import test from 'node:test' import { closestArrivalsByRoute, displayName, displayStopName, haversineMeters, isTerminalArrival, lineBearingAt, matchesSearch, realtimeArrivalText, resolveBusApiBase, sortArrivalsByEstimatedTime, unavailableReasonTextKey, vehicleBearingAt, vehicleLocationText } from './core.mjs' import { closestArrivalsByRoute, displayName, displayStopName, haversineMeters, isTerminalArrival, lineBearingAt, matchesSearch, realtimeArrivalText, sortArrivalsByEstimatedTime, unavailableReasonTextKey, vehicleBearingAt, vehicleLocationText } from './core.mjs' import { renderNoticeMarkdown } from './markdown.mjs' import { isCampusRoute, orderedStopProgress, serviceTypeLabel, vehicleRouteLabel, vehicleTextColor } from './core.mjs' test('API base uses fixed development and production addresses', () => { assert.equal(resolveBusApiBase(), 'http://127.0.0.1:8000') assert.equal(resolveBusApiBase(true), 'https://buseta.sustcra.com') }) test('realtime vehicles use campus parents and compact route text with icon colors', () => { for (const id of ['line1', 'line2', 'k1', 'k2']) assert.equal(isCampusRoute(id), true) for (const id of ['ipark', 'sofun', 'k1a', 'K1A', '', undefined]) assert.equal(isCampusRoute(id), false) assert.deepEqual(['line1', 'line2', 'k1', 'K1A', 'k2', 'K2A', 'unknown'].map(vehicleRouteLabel), ['1', '2', 'K1', 'K1', 'K2', 'K2', '']) assert.equal(vehicleTextColor('BUS'), '#00451e') assert.equal(vehicleTextColor('shuttle'), '#7e22ce') assert.equal(vehicleTextColor('BUS'), '#754600') assert.equal(vehicleTextColor('shuttle'), '#193e29') assert.equal(serviceTypeLabel('SHORT_TURN'), '区间车') assert.equal(serviceTypeLabel('EXPRESS', 'en'), 'Express') }) Loading Loading
docs/.vuepress/components/bus-v2/BusMapV2.vue +4 −2 Changes for docs/.vuepress/components/bus-v2/BusMapV2.vue: 4 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -139,6 +139,7 @@ function updateVehicleMarker(record, vehicle) { record.element.setAttribute('aria-label', `${label} · ${record.element.title}`) record.label.textContent = label record.label.classList.toggle('bus-map__vehicle-label--compact', label.length > 1) record.label.classList.toggle('bus-map__vehicle-label--shuttle', String(vehicle.vehicle_type).toUpperCase() === 'SHUTTLE') record.label.style.color = vehicleTextColor(vehicle.vehicle_type) record.element.style.setProperty('--route-color', route?.color || '#2878c8') const bearing = vehicleBearing(vehicle) Loading Loading @@ -374,9 +375,10 @@ defineExpose({ refresh, refreshVehicleMarkers }) .bus-map__legend { position: absolute; z-index: 1; bottom: .75rem; left: .75rem; margin: 0; padding: .4rem .55rem; border-radius: .35rem; background: color-mix(in srgb, var(--bus-v2-bg, #fff) 90%, transparent); } .bus-map__overlap { position: absolute; z-index: 1; top: .75rem; left: 3rem; padding: .35rem .5rem; border-radius: .35rem; background: color-mix(in srgb, var(--bus-v2-bg, #fff) 90%, transparent); color: var(--bus-v2-text, #333); font-size: .8rem; } .bus-map :deep(.bus-map__vehicle-marker) { display: block; width: 1.6rem; min-width: 1.6rem; max-width: 1.6rem; height: 1.6rem; min-height: 1.6rem; max-height: 1.6rem; line-height: 0; } .bus-map :deep(.bus-map__vehicle) { box-sizing: border-box; display: block; position: relative; width: 1.6rem; min-width: 1.6rem; max-width: 1.6rem; height: 1.6rem; min-height: 1.6rem; max-height: 1.6rem; aspect-ratio: 1; border: 2px solid #fff; border-radius: 50%; padding: 1px; background: var(--route-color); cursor: pointer; } .bus-map :deep(.bus-map__vehicle-label) { position: absolute; inset: 2px; display: flex; align-items: center; justify-content: center; border-radius: 50%; background: #fff; font: 700 .9rem/1 "Microsoft YaHei UI", "Microsoft YaHei", "PingFang SC", sans-serif; } .bus-map :deep(.bus-map__vehicle) { box-sizing: border-box; display: block; position: relative; width: 1.6rem; min-width: 1.6rem; max-width: 1.6rem; height: 1.6rem; min-height: 1.6rem; max-height: 1.6rem; aspect-ratio: 1; border: 2px solid #fff; border-radius: 50%; padding: 1px; background: var(--route-color); box-shadow: 0 1px 4px #0005; cursor: pointer; } .bus-map :deep(.bus-map__vehicle-label) { position: absolute; inset: 2px; display: flex; align-items: center; justify-content: center; border-radius: 50%; background: var(--bus-v2-bus-fill); font: 700 .9rem/1 "Microsoft YaHei UI", "Microsoft YaHei", "PingFang SC", sans-serif; } .bus-map :deep(.bus-map__vehicle-label--compact) { font-size: .65rem; } .bus-map :deep(.bus-map__vehicle-label--shuttle) { background: var(--bus-v2-shuttle-fill); } .bus-map :deep(.bus-map__vehicle-arrow) { position: absolute; z-index: 2; inset: 0; pointer-events: none; transform: rotate(var(--bearing)); } .bus-map :deep(.bus-map__vehicle-arrow)::before { content: ''; position: absolute; top: -.57rem; left: 50%; transform: translateX(-50%); border-right: .44rem solid transparent; border-bottom: .62rem solid #fff; border-left: .44rem solid transparent; } .bus-map :deep(.bus-map__vehicle-arrow)::after { content: ''; position: absolute; top: -.45rem; left: 50%; transform: translateX(-50%); border-right: .32rem solid transparent; border-bottom: .5rem solid var(--route-color); border-left: .32rem solid transparent; } Loading
docs/.vuepress/components/bus-v2/BusScheduleRowsV2.vue +3 −4 Changes for docs/.vuepress/components/bus-v2/BusScheduleRowsV2.vue: 3 added lines, 4 removed lines. Original line number Diff line number Diff line Loading @@ -21,13 +21,12 @@ <script setup> import { onBeforeUnmount, onMounted, ref } from 'vue' import { serviceTypeLabel, vehicleTextColor } from './core.mjs' import { serviceTypeLabel } from './core.mjs' const props = defineProps({ groups: { type: Array, default: () => [] }, language: { type: String, default: 'zh' } }) const activeOnly = ref(true) const collapsedRoutes = ref(new Set()) const portrait = ref(false) const busColor = vehicleTextColor('BUS'), shuttleColor = vehicleTextColor('SHUTTLE') let orientationQuery const text = (zh, en) => props.language === 'zh' ? zh : en const changeLabel = (item) => ({ EXTRA: text('加班', 'Extra'), CANCELLED: text('停运', 'Cancelled') })[item.change] || '' Loading @@ -42,9 +41,9 @@ onBeforeUnmount(() => { orientationQuery?.removeEventListener?.('change', update </script> <style scoped lang="scss"> .schedule-toolbar { display: flex; flex-wrap: wrap; gap: .55rem 1rem; align-items: center; justify-content: space-between; margin-bottom: .7rem; font-size: .84rem; }.schedule-legend { display: flex; flex-wrap: wrap; gap: .55rem 1rem; align-items: center; }.schedule-legend > span { display: inline-flex; align-items: center; gap: .3rem; }.schedule-filter { cursor: pointer; white-space: nowrap; }.swatch { width: .72rem; height: .72rem; border-radius: 50%; }.swatch.bus { background: v-bind(busColor); }.swatch.shuttle { background: v-bind(shuttleColor); }.schedule-list { overflow: hidden; border: 1px solid #d9e2ec; border-radius: .6rem; container-type: inline-size; }.schedule-row { display: flex; flex-wrap: wrap; border-bottom: 1px solid #e7edf4; }.schedule-row:last-child { border: 0; }.route-info { box-sizing: border-box; display: flex; min-width: 8rem; flex: 1 1 9rem; flex-direction: column; justify-content: center; gap: .2rem; padding: .75rem; border-left: .4rem solid; background: var(--c-bg-soft, #f8fafc); }.route-info small, .muted { color: #667085; }.times { box-sizing: border-box; display: flex; min-width: 8rem; flex: 999 1 8rem; flex-wrap: wrap; align-content: flex-start; gap: .45rem; padding: .75rem; }.time { --vehicle-color: v-bind(busColor); padding: .13rem .38rem; border-radius: .25rem; color: var(--vehicle-color); background: color-mix(in srgb, var(--vehicle-color) 6%, #fff); font-family: ui-monospace, SFMono-Regular, Consolas, monospace; }.time.shuttle { --vehicle-color: v-bind(shuttleColor); }.time.running { border: 1px solid var(--vehicle-color); background: color-mix(in srgb, var(--vehicle-color) 10%, #fff); }.time.next { background: var(--vehicle-color); color: #fff; font-weight: 700; }.time.past { opacity: .35; } .schedule-toolbar { display: flex; flex-wrap: wrap; gap: .55rem 1rem; align-items: center; justify-content: space-between; margin-bottom: .7rem; font-size: .84rem; }.schedule-legend { display: flex; flex-wrap: wrap; gap: .55rem 1rem; align-items: center; }.schedule-legend > span { display: inline-flex; align-items: center; gap: .3rem; }.schedule-filter { cursor: pointer; white-space: nowrap; }.swatch { width: .72rem; height: .72rem; border-radius: 50%; }.swatch.bus { background: var(--bus-v2-bus-fill); border: 1px solid var(--bus-v2-bus-text); }.swatch.shuttle { background: var(--bus-v2-shuttle-fill); border: 1px solid var(--bus-v2-shuttle-text); }.schedule-list { overflow: hidden; border: 1px solid #d9e2ec; border-radius: .6rem; container-type: inline-size; }.schedule-row { display: flex; flex-wrap: wrap; border-bottom: 1px solid #e7edf4; }.schedule-row:last-child { border: 0; }.route-info { box-sizing: border-box; display: flex; min-width: 8rem; flex: 1 1 9rem; flex-direction: column; justify-content: center; gap: .2rem; padding: .75rem; border-left: .4rem solid; background: var(--c-bg-soft, #f8fafc); }.route-info small, .muted { color: #667085; }.times { box-sizing: border-box; display: flex; min-width: 8rem; flex: 999 1 8rem; flex-wrap: wrap; align-content: flex-start; gap: .45rem; padding: .75rem; }.time { --vehicle-color: var(--bus-v2-bus-text); --vehicle-soft: var(--bus-v2-bus-soft); --vehicle-fill: var(--bus-v2-bus-fill); --vehicle-ink: #754600; padding: .13rem .38rem; border-radius: .25rem; color: var(--vehicle-color); background: var(--vehicle-soft); font-family: ui-monospace, SFMono-Regular, Consolas, monospace; }.time.shuttle { --vehicle-color: var(--bus-v2-shuttle-text); --vehicle-soft: var(--bus-v2-shuttle-soft); --vehicle-fill: var(--bus-v2-shuttle-fill); --vehicle-ink: #193e29; }.time.running { border: 1px solid var(--vehicle-color); background: var(--vehicle-soft); }.time.next { background: var(--vehicle-fill); color: var(--vehicle-ink); font-weight: 700; }.time.past { opacity: .35; } @container (max-width: 24rem) { .schedule-row { flex-direction: column; }.route-info { width: 100%; min-width: 0; flex: none; flex-direction: row; flex-wrap: wrap; align-items: baseline; justify-content: flex-start; border-left: 0; border-top: .35rem solid; padding: .5rem; }.times { width: 100%; min-width: 0; flex: none; } } .schedule-list { border-color: var(--bus-v2-border); background: var(--bus-v2-bg); color: var(--bus-v2-text); } .schedule-list { --bus-v2-bg: var(--bus-v2-schedule-bg); border-color: var(--bus-v2-border); background: var(--bus-v2-bg); color: var(--bus-v2-text); } .schedule-row { border-color: var(--bus-v2-border); } .route-info { background: var(--bus-v2-bg-alt); } .route-info.collapsible { cursor: pointer; } Loading
docs/.vuepress/components/bus-v2/BusVehicleLegendV2.vue +3 −2 Changes for docs/.vuepress/components/bus-v2/BusVehicleLegendV2.vue: 3 added lines, 2 removed lines. Original line number Diff line number Diff line <template> <div class="vehicle-legend" :aria-label="language === 'zh' ? '车型图例' : 'Vehicle legend'"> <span><i v-if="textMarkers" class="vehicle-legend__marker" :style="{ color: vehicleTextColor('BUS') }" aria-hidden="true">1</i><img v-else src="/bus.png" alt="">{{ language === 'zh' ? '巴士' : 'Bus' }}</span> <span><i v-if="textMarkers" class="vehicle-legend__marker" :style="{ color: vehicleTextColor('SHUTTLE') }" aria-hidden="true">1</i><img v-else src="/sev.png" alt="">{{ language === 'zh' ? '电瓶车' : 'EV Shuttle' }}</span> <span><i v-if="textMarkers" class="vehicle-legend__marker shuttle" :style="{ color: vehicleTextColor('SHUTTLE') }" aria-hidden="true">1</i><img v-else src="/sev.png" alt="">{{ language === 'zh' ? '电瓶车' : 'EV Shuttle' }}</span> </div> </template> Loading @@ -13,5 +13,6 @@ defineProps({ language: { type: String, default: 'zh' }, textMarkers: Boolean }) <style scoped> .vehicle-legend { display: flex; flex-wrap: wrap; gap: .6rem; margin-top: .5rem; color: var(--bus-v2-muted); font-size: .82rem; }.vehicle-legend span { display: inline-flex; align-items: center; gap: .25rem; white-space: nowrap; } .vehicle-legend img { box-sizing: border-box; width: 1rem; height: 1rem; padding: 1px; border-radius: 50%; background: #fff; } .vehicle-legend__marker { display: inline-flex; align-items: center; justify-content: center; width: 1.25rem; height: 1.25rem; border-radius: 50%; background: #fff; font: 700 .75rem/1 "Microsoft YaHei UI", "Microsoft YaHei", "PingFang SC", sans-serif; } .vehicle-legend__marker { display: inline-flex; align-items: center; justify-content: center; width: 1.25rem; height: 1.25rem; border-radius: 50%; background: var(--bus-v2-bus-fill); font: 700 .75rem/1 "Microsoft YaHei UI", "Microsoft YaHei", "PingFang SC", sans-serif; } .vehicle-legend__marker.shuttle { background: var(--bus-v2-shuttle-fill); } </style>
docs/.vuepress/components/bus-v2/core.mjs +2 −2 Changes for docs/.vuepress/components/bus-v2/core.mjs: 2 added lines, 2 removed lines. Original line number Diff line number Diff line const DEV_API_BASE = 'http://127.0.0.1:8000' const DEV_API_BASE = 'https://buseta.sustcra.com' const PROD_API_BASE = 'https://buseta.sustcra.com' export const resolveBusApiBase = (production = false) => production ? PROD_API_BASE : DEV_API_BASE export const isCampusRoute = (routeId) => ['line1', 'line2', 'k1', 'k2'].includes(routeId) export const vehicleRouteLabel = (routeId) => ({ line1: '1', line2: '2', k1: 'K1', k1a: 'K1', k2: 'K2', k2a: 'K2' })[String(routeId || '').toLowerCase()] || '' export const vehicleTextColor = (vehicleType) => String(vehicleType).toUpperCase() === 'SHUTTLE' ? '#7e22ce' : '#00451e' export const vehicleTextColor = (vehicleType) => String(vehicleType).toUpperCase() === 'SHUTTLE' ? '#193e29' : '#754600' export function serviceTypeLabel(serviceType, language = 'zh') { const labels = { NORMAL: ['常规服务', 'Normal service'], SHORT_TURN: ['区间车', 'Short turn'], EXPRESS: ['快线', 'Express'] } return labels[serviceType]?.[language === 'en' ? 1 : 0] || serviceType || '' Loading
docs/.vuepress/components/bus-v2/core.test.mjs +3 −8 Changes for docs/.vuepress/components/bus-v2/core.test.mjs: 3 added lines, 8 removed lines. Original line number Diff line number Diff line import assert from 'node:assert/strict' import test from 'node:test' import { closestArrivalsByRoute, displayName, displayStopName, haversineMeters, isTerminalArrival, lineBearingAt, matchesSearch, realtimeArrivalText, resolveBusApiBase, sortArrivalsByEstimatedTime, unavailableReasonTextKey, vehicleBearingAt, vehicleLocationText } from './core.mjs' import { closestArrivalsByRoute, displayName, displayStopName, haversineMeters, isTerminalArrival, lineBearingAt, matchesSearch, realtimeArrivalText, sortArrivalsByEstimatedTime, unavailableReasonTextKey, vehicleBearingAt, vehicleLocationText } from './core.mjs' import { renderNoticeMarkdown } from './markdown.mjs' import { isCampusRoute, orderedStopProgress, serviceTypeLabel, vehicleRouteLabel, vehicleTextColor } from './core.mjs' test('API base uses fixed development and production addresses', () => { assert.equal(resolveBusApiBase(), 'http://127.0.0.1:8000') assert.equal(resolveBusApiBase(true), 'https://buseta.sustcra.com') }) test('realtime vehicles use campus parents and compact route text with icon colors', () => { for (const id of ['line1', 'line2', 'k1', 'k2']) assert.equal(isCampusRoute(id), true) for (const id of ['ipark', 'sofun', 'k1a', 'K1A', '', undefined]) assert.equal(isCampusRoute(id), false) assert.deepEqual(['line1', 'line2', 'k1', 'K1A', 'k2', 'K2A', 'unknown'].map(vehicleRouteLabel), ['1', '2', 'K1', 'K1', 'K2', 'K2', '']) assert.equal(vehicleTextColor('BUS'), '#00451e') assert.equal(vehicleTextColor('shuttle'), '#7e22ce') assert.equal(vehicleTextColor('BUS'), '#754600') assert.equal(vehicleTextColor('shuttle'), '#193e29') assert.equal(serviceTypeLabel('SHORT_TURN'), '区间车') assert.equal(serviceTypeLabel('EXPRESS', 'en'), 'Express') }) Loading