mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-08-07 21:56:33 -05:00
1219 lines
25 KiB
CSS
1219 lines
25 KiB
CSS
/*
|
|
Scanner feature styles, ported from emberz. Scoped under .scanner-app so nothing
|
|
leaks into the host app; design tokens come from sendou.ink's own
|
|
app/styles/vars.css (the emberz copies of those tokens were dropped).
|
|
*/
|
|
.scanner-app {
|
|
container: scanner / inline-size;
|
|
}
|
|
|
|
.scanner-app .topbar {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--s-6);
|
|
padding-bottom: var(--s-3);
|
|
border-bottom: var(--border-width) solid var(--color-bg-high);
|
|
margin-bottom: var(--s-4);
|
|
}
|
|
|
|
.scanner-app .topbar nav {
|
|
display: flex;
|
|
gap: var(--s-2);
|
|
}
|
|
|
|
.scanner-app .topbar nav a {
|
|
color: var(--color-text-high);
|
|
font-size: var(--font-xs);
|
|
font-weight: var(--weight-bold);
|
|
text-decoration: none;
|
|
padding: var(--s-1) var(--s-2-5);
|
|
border-radius: var(--radius-field);
|
|
}
|
|
|
|
.scanner-app .topbar nav a:hover {
|
|
color: var(--color-text);
|
|
}
|
|
|
|
.scanner-app .topbar nav a:focus-visible {
|
|
outline: var(--focus-ring);
|
|
outline-offset: 1px;
|
|
}
|
|
|
|
.scanner-app .topbar nav a.active {
|
|
color: var(--color-text-accent);
|
|
background: var(--color-bg-high);
|
|
}
|
|
|
|
.scanner-app button {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: var(--s-1-5);
|
|
border: var(--border-style-accent);
|
|
border-radius: var(--radius-field);
|
|
appearance: none;
|
|
background: var(--color-text-accent);
|
|
color: var(--color-text-inverse);
|
|
cursor: pointer;
|
|
font-family: inherit;
|
|
font-size: var(--font-xs);
|
|
font-weight: var(--weight-bold);
|
|
padding: 0 var(--field-padding);
|
|
height: var(--field-size-sm);
|
|
white-space: nowrap;
|
|
user-select: none;
|
|
|
|
& > svg {
|
|
width: 16px;
|
|
height: 16px;
|
|
}
|
|
}
|
|
|
|
.scanner-app button.outlined {
|
|
background: transparent;
|
|
color: var(--color-text-accent);
|
|
}
|
|
|
|
.scanner-app button:focus-visible {
|
|
outline: var(--focus-ring);
|
|
outline-offset: 1px;
|
|
}
|
|
|
|
.scanner-app button:active {
|
|
transform: translateY(1px);
|
|
}
|
|
|
|
.scanner-app button:disabled {
|
|
cursor: not-allowed;
|
|
opacity: 0.5;
|
|
transform: initial;
|
|
}
|
|
|
|
/* the global select is full width, which would break the controls row */
|
|
.scanner-app select {
|
|
appearance: none;
|
|
width: auto;
|
|
max-width: 20rem;
|
|
border: var(--border-style);
|
|
border-radius: var(--radius-field);
|
|
background-color: var(--color-bg);
|
|
background-image: var(--field-icon-chevron);
|
|
background-position: center right var(--field-padding);
|
|
background-size: var(--field-size-icon) auto;
|
|
background-repeat: no-repeat;
|
|
color: var(--color-text);
|
|
cursor: pointer;
|
|
font-family: inherit;
|
|
font-size: var(--font-xs);
|
|
height: var(--field-size-sm);
|
|
padding: 0 calc(var(--field-size-icon) + var(--field-padding) + var(--s-2)) 0
|
|
var(--field-padding);
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
outline: none;
|
|
}
|
|
|
|
.scanner-app select:focus-within {
|
|
outline: var(--focus-ring);
|
|
outline-offset: 1px;
|
|
}
|
|
|
|
.scanner-app select:disabled {
|
|
cursor: not-allowed;
|
|
opacity: 0.5;
|
|
}
|
|
|
|
.scanner-app .controls {
|
|
display: flex;
|
|
gap: var(--s-2);
|
|
align-items: center;
|
|
margin-bottom: var(--s-3);
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.scanner-app .status {
|
|
padding: var(--s-0-5) var(--s-3);
|
|
border-radius: var(--radius-full);
|
|
font-size: var(--font-2xs);
|
|
font-weight: var(--weight-bold);
|
|
border: var(--border-style);
|
|
}
|
|
|
|
.scanner-app .status.idle {
|
|
color: var(--color-text-high);
|
|
}
|
|
|
|
.scanner-app .status.watching {
|
|
color: var(--color-info-high);
|
|
border-color: var(--color-info-low);
|
|
background: var(--color-info-low);
|
|
}
|
|
|
|
.scanner-app .status.detected {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 5px;
|
|
color: var(--color-success-high);
|
|
border-color: var(--color-success-low);
|
|
background: var(--color-success-low);
|
|
}
|
|
|
|
.scanner-app .live-layout {
|
|
display: grid;
|
|
grid-template-columns: minmax(320px, 640px) minmax(0, 1fr);
|
|
gap: var(--s-4);
|
|
align-items: start;
|
|
}
|
|
|
|
.scanner-app video.preview,
|
|
.scanner-app canvas.preview {
|
|
width: 100%;
|
|
background: #000;
|
|
border-radius: var(--radius-box);
|
|
border: var(--border-width) solid var(--color-bg-high);
|
|
}
|
|
|
|
.scanner-app .feed {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--s-3);
|
|
}
|
|
|
|
.scanner-app .match-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--s-3);
|
|
}
|
|
|
|
.scanner-app .card {
|
|
background: var(--color-bg-high);
|
|
border-radius: var(--radius-box);
|
|
padding: var(--s-4);
|
|
}
|
|
|
|
.scanner-app .card .meta {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: var(--s-2) var(--s-4);
|
|
color: var(--color-text-high);
|
|
font-size: var(--font-2xs);
|
|
font-weight: var(--weight-semi);
|
|
font-variant-numeric: tabular-nums;
|
|
margin-bottom: var(--s-2);
|
|
align-items: center;
|
|
|
|
/* time + confidence + event type read as one unit, the detail text after it
|
|
sits further away */
|
|
& .meta-pills {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
gap: var(--s-1);
|
|
}
|
|
|
|
/* event-type chip: neutral like the match chips, the icon carries the accent */
|
|
& .status.detected {
|
|
gap: 5px;
|
|
padding: var(--s-0-5) var(--s-2-5);
|
|
color: var(--color-text);
|
|
border-color: var(--color-border);
|
|
background: var(--color-bg);
|
|
|
|
& .event-type-icon {
|
|
color: var(--color-text-accent);
|
|
}
|
|
}
|
|
|
|
/* time + confidence chips share the event-type chip look; icon documents
|
|
the value, full label on hover via title */
|
|
& .meta-chip {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 5px;
|
|
padding: var(--s-0-5) var(--s-2-5);
|
|
border-radius: var(--radius-full);
|
|
border: var(--border-style);
|
|
background: var(--color-bg);
|
|
color: var(--color-text);
|
|
font-weight: var(--weight-bold);
|
|
|
|
& .meta-chip-icon {
|
|
color: var(--color-text-accent);
|
|
}
|
|
}
|
|
}
|
|
|
|
.scanner-app .card img.thumb {
|
|
display: block;
|
|
height: 40px;
|
|
width: auto;
|
|
border-radius: var(--radius-field);
|
|
}
|
|
|
|
.scanner-app .teams {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
|
|
gap: var(--s-2);
|
|
|
|
/* single hug-content box (own-results cards) */
|
|
&.solo {
|
|
grid-template-columns: max-content;
|
|
}
|
|
|
|
/* single full-width box (death cards) */
|
|
&.death {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
|
|
/* sendou.ink send status strip under a feed card */
|
|
.scanner-app .send-wrap {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--s-0-5);
|
|
}
|
|
|
|
.scanner-app .send-strip {
|
|
display: flex;
|
|
gap: var(--s-3);
|
|
align-items: center;
|
|
padding: var(--s-1) var(--s-4);
|
|
border-radius: var(--radius-box);
|
|
background: var(--color-bg-high);
|
|
font-size: var(--font-2xs);
|
|
font-weight: var(--weight-semi);
|
|
color: var(--color-text-high);
|
|
}
|
|
|
|
.scanner-app .send-strip.sent {
|
|
color: var(--color-success-high);
|
|
background: var(--color-success-low);
|
|
}
|
|
|
|
.scanner-app .send-strip.failed {
|
|
color: var(--color-error-high);
|
|
background: var(--color-error-low);
|
|
}
|
|
|
|
.scanner-app .send-strip.queued,
|
|
.scanner-app .send-strip.sending {
|
|
color: var(--color-info-high);
|
|
background: var(--color-info-low);
|
|
}
|
|
|
|
.scanner-app .score {
|
|
color: var(--color-text-high);
|
|
font-size: var(--font-2xs);
|
|
}
|
|
|
|
.scanner-app .error {
|
|
color: var(--color-error);
|
|
}
|
|
|
|
.scanner-app .send-strip .error {
|
|
color: inherit;
|
|
font-weight: var(--weight-body);
|
|
}
|
|
|
|
/* lighter than a solid slab: translucent fill + hairline so the boxes read
|
|
as grouping, not chrome */
|
|
.scanner-app .team {
|
|
border-radius: var(--radius-field);
|
|
padding: var(--s-2) var(--s-2-5);
|
|
background: color-mix(in oklab, var(--color-bg) 45%, transparent);
|
|
border: 1px solid color-mix(in oklab, var(--color-border) 55%, transparent);
|
|
min-width: 0;
|
|
overflow-x: auto;
|
|
}
|
|
|
|
.scanner-app .minimap-player {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--s-2);
|
|
padding-block: 3px;
|
|
font-size: var(--font-xs);
|
|
|
|
& .weapon-missing {
|
|
flex-shrink: 0;
|
|
width: 24px;
|
|
text-align: center;
|
|
color: var(--color-text-high);
|
|
}
|
|
|
|
& .name {
|
|
flex: 1 1 0;
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
& .abilities {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--s-0-5);
|
|
flex-shrink: 0;
|
|
}
|
|
}
|
|
|
|
/* d-pad chevron / face-button pucks, same round-solid language as weapon icons */
|
|
.scanner-app .slot-marker {
|
|
flex-shrink: 0;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 20px;
|
|
height: 20px;
|
|
border-radius: var(--radius-full);
|
|
background: var(--color-bg-badge);
|
|
color: #fff;
|
|
font-size: var(--font-2xs);
|
|
font-weight: var(--weight-bold);
|
|
line-height: 1;
|
|
|
|
& svg {
|
|
width: 13px;
|
|
height: 13px;
|
|
}
|
|
|
|
&.right svg {
|
|
transform: rotate(90deg);
|
|
}
|
|
|
|
&.down svg {
|
|
transform: rotate(180deg);
|
|
}
|
|
|
|
&.left svg {
|
|
transform: rotate(-90deg);
|
|
}
|
|
}
|
|
|
|
.scanner-app .death-body {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--s-3);
|
|
padding-block: var(--s-0-5);
|
|
|
|
& .death-info {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--s-0-5);
|
|
min-width: 0;
|
|
|
|
& .death-name {
|
|
font-size: var(--font-xs);
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
& .death-weapon {
|
|
font-size: var(--font-2xs);
|
|
font-weight: var(--weight-semi);
|
|
color: var(--color-text-high);
|
|
}
|
|
}
|
|
|
|
& .death-abilities {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--s-2-5);
|
|
margin-inline-start: auto;
|
|
flex-wrap: wrap;
|
|
|
|
& .gear {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--s-0-5);
|
|
}
|
|
}
|
|
}
|
|
|
|
.scanner-app .team h3 {
|
|
margin: 0 0 var(--s-1-5);
|
|
font-size: var(--font-2xs);
|
|
font-weight: var(--weight-bold);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.06em;
|
|
color: var(--color-text-high);
|
|
}
|
|
|
|
.scanner-app .team.win h3 {
|
|
color: var(--color-error-high);
|
|
}
|
|
|
|
.scanner-app .team.lose h3 {
|
|
color: var(--color-info-high);
|
|
}
|
|
|
|
.scanner-app table.players {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
font-size: var(--font-xs);
|
|
}
|
|
|
|
.scanner-app table.players td {
|
|
padding: var(--s-0-5) var(--s-1-5);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.scanner-app table.players td.num {
|
|
text-align: right;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
.scanner-app .teams.solo table.players {
|
|
width: auto;
|
|
}
|
|
|
|
/* round black pucks, same language as the match header weapon row */
|
|
.scanner-app img.weapon-icon {
|
|
width: 28px;
|
|
height: 28px;
|
|
vertical-align: middle;
|
|
background: var(--color-bg-badge);
|
|
border-radius: var(--radius-full);
|
|
padding: var(--s-0-5);
|
|
}
|
|
|
|
.scanner-app .minimap-player img.weapon-icon {
|
|
flex-shrink: 0;
|
|
width: 24px;
|
|
height: 24px;
|
|
}
|
|
|
|
.scanner-app .weapon-cell {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: var(--s-1);
|
|
}
|
|
|
|
.scanner-app button.ability-trigger {
|
|
height: auto;
|
|
padding: var(--s-0-5);
|
|
background: var(--color-bg-higher);
|
|
border: 1px solid var(--color-border-high);
|
|
border-radius: var(--radius-selector);
|
|
}
|
|
|
|
.scanner-app button.ability-trigger img {
|
|
display: block;
|
|
width: var(--field-size-icon);
|
|
height: var(--field-size-icon);
|
|
}
|
|
|
|
.scanner-app .vod-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--s-2);
|
|
}
|
|
|
|
.scanner-app .vod-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--s-3);
|
|
background: var(--color-bg-high);
|
|
border-radius: var(--radius-box);
|
|
padding: var(--s-3) var(--s-4);
|
|
}
|
|
|
|
.scanner-app .vod-item .name {
|
|
font-size: var(--font-sm);
|
|
font-weight: var(--weight-bold);
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.scanner-app .vod-item .score {
|
|
flex: 1;
|
|
}
|
|
|
|
/* the shared destructive variant loses to `.scanner-app button` on specificity */
|
|
.scanner-app button.vod-delete {
|
|
width: var(--field-size-sm);
|
|
padding: 0;
|
|
border-color: var(--color-error);
|
|
background: var(--color-error);
|
|
outline-color: var(--color-error);
|
|
}
|
|
|
|
.scanner-app .dropzone {
|
|
border: var(--border-width) dashed var(--color-border-high);
|
|
border-radius: var(--radius-box);
|
|
padding: var(--s-8);
|
|
text-align: center;
|
|
color: var(--color-text-high);
|
|
font-size: var(--font-sm);
|
|
font-weight: var(--weight-semi);
|
|
margin-bottom: var(--s-4);
|
|
}
|
|
|
|
.scanner-app .dropzone label {
|
|
display: inline;
|
|
font-size: inherit;
|
|
font-weight: inherit;
|
|
margin-block-end: 0;
|
|
text-decoration: underline;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.scanner-app .dropzone.over {
|
|
border-color: var(--color-text-accent);
|
|
color: var(--color-text-accent);
|
|
}
|
|
|
|
.scanner-app .screenshot-frame {
|
|
position: relative;
|
|
margin-bottom: var(--s-4);
|
|
}
|
|
|
|
.scanner-app .screenshot-frame canvas {
|
|
width: 100%;
|
|
border-radius: var(--radius-box);
|
|
border: var(--border-width) solid var(--color-bg-high);
|
|
}
|
|
|
|
.scanner-app table.inspector {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
font-size: var(--font-xs);
|
|
}
|
|
|
|
.scanner-app table.inspector th {
|
|
text-align: left;
|
|
color: var(--color-text-high);
|
|
font-size: var(--font-2xs);
|
|
font-weight: var(--weight-bold);
|
|
padding: var(--s-1-5);
|
|
border-bottom: var(--border-style);
|
|
}
|
|
|
|
.scanner-app table.inspector td {
|
|
padding: var(--s-1-5);
|
|
border-bottom: 1px solid var(--color-bg-high);
|
|
vertical-align: middle;
|
|
}
|
|
|
|
.scanner-app table.inspector canvas {
|
|
display: block;
|
|
background: #000;
|
|
border-radius: var(--radius-selector);
|
|
}
|
|
|
|
.scanner-app .candidates {
|
|
display: flex;
|
|
gap: var(--s-2);
|
|
align-items: center;
|
|
}
|
|
|
|
.scanner-app .candidates .cand {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: var(--s-0-5);
|
|
font-size: var(--font-2xs);
|
|
color: var(--color-text-high);
|
|
}
|
|
|
|
.scanner-app .weapon-candidates .cand:first-child {
|
|
color: var(--color-success);
|
|
}
|
|
|
|
/* internal navigation styled like the sibling action buttons */
|
|
.scanner-app .link-button {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: var(--s-1-5);
|
|
padding: 0 var(--field-padding);
|
|
height: var(--field-size-sm);
|
|
border: var(--border-style-accent);
|
|
border-radius: var(--radius-field);
|
|
background: var(--color-text-accent);
|
|
color: var(--color-text-inverse);
|
|
font-size: var(--font-xs);
|
|
font-weight: var(--weight-bold);
|
|
white-space: nowrap;
|
|
text-decoration: none;
|
|
|
|
& > svg {
|
|
width: 16px;
|
|
height: 16px;
|
|
}
|
|
|
|
&:active {
|
|
transform: translateY(1px);
|
|
}
|
|
|
|
&:focus-visible {
|
|
outline: var(--focus-ring);
|
|
outline-offset: 1px;
|
|
}
|
|
}
|
|
|
|
/* secondary actions live behind this icon-only trigger, pushed to the row's end */
|
|
.scanner-app button.icon-menu {
|
|
margin-inline-start: auto;
|
|
width: var(--field-size-sm);
|
|
padding: 0;
|
|
border: var(--border-style);
|
|
background: var(--color-bg-higher);
|
|
color: var(--color-text);
|
|
|
|
&:hover {
|
|
color: var(--color-text-accent);
|
|
}
|
|
|
|
& > svg {
|
|
width: var(--field-size-icon);
|
|
height: var(--field-size-icon);
|
|
margin: 0;
|
|
}
|
|
}
|
|
|
|
/* ── ingested matches feed ─────────────────────────────────────────── */
|
|
|
|
.scanner-app .set-divider {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--s-2-5);
|
|
margin-top: var(--s-1-5);
|
|
color: var(--color-text-high);
|
|
font-size: var(--font-2xs);
|
|
font-weight: var(--weight-semi);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.08em;
|
|
|
|
&::before,
|
|
&::after {
|
|
content: "";
|
|
flex: 1;
|
|
height: var(--border-width);
|
|
background-color: var(--color-border);
|
|
}
|
|
}
|
|
|
|
.scanner-app .match-card {
|
|
container: match-card / inline-size;
|
|
position: sticky;
|
|
top: var(--layout-sticky-top);
|
|
z-index: 1;
|
|
overflow: hidden;
|
|
border-radius: var(--radius-box);
|
|
background-color: var(--color-bg-high);
|
|
|
|
/* only cards the scan just formed animate in — see MatchCard's `enter` */
|
|
&.enter {
|
|
animation: scanner-card-in 0.4s cubic-bezier(0.16, 1, 0.3, 1) both;
|
|
}
|
|
|
|
/* one overlay element all send-state effects draw on */
|
|
&::after {
|
|
content: "";
|
|
position: absolute;
|
|
inset: 0;
|
|
border-radius: inherit;
|
|
pointer-events: none;
|
|
}
|
|
|
|
&.sending {
|
|
box-shadow: inset 0 0 0 1px var(--color-info-low);
|
|
}
|
|
|
|
&.sending::after {
|
|
background-image: linear-gradient(
|
|
105deg,
|
|
transparent 40%,
|
|
rgb(255 255 255 / 0.09) 50%,
|
|
transparent 60%
|
|
);
|
|
background-size: 250% 100%;
|
|
animation: scanner-sheen 1.4s linear infinite;
|
|
}
|
|
|
|
&.flash-sent::after {
|
|
animation: scanner-sent-pop 0.8s ease-out both;
|
|
}
|
|
|
|
&.live {
|
|
box-shadow: inset 0 0 0 1px var(--color-info-low);
|
|
}
|
|
}
|
|
|
|
@keyframes scanner-card-in {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(10px) scale(0.98);
|
|
}
|
|
}
|
|
|
|
@keyframes scanner-sheen {
|
|
from {
|
|
background-position: 130% 0;
|
|
}
|
|
to {
|
|
background-position: -130% 0;
|
|
}
|
|
}
|
|
|
|
@keyframes scanner-sent-pop {
|
|
0% {
|
|
opacity: 1;
|
|
box-shadow:
|
|
inset 0 0 0 2px var(--color-success),
|
|
inset 0 0 32px var(--color-success-low);
|
|
}
|
|
100% {
|
|
opacity: 0;
|
|
box-shadow:
|
|
inset 0 0 0 2px var(--color-success),
|
|
inset 0 0 32px var(--color-success-low);
|
|
}
|
|
}
|
|
|
|
.scanner-app .match-card-main {
|
|
display: flex;
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
gap: var(--s-2) var(--s-3);
|
|
padding: var(--s-3) var(--s-4);
|
|
min-height: 66px;
|
|
}
|
|
|
|
.scanner-app .match-card.flash-failed .match-card-main {
|
|
animation: scanner-shake 0.35s ease;
|
|
}
|
|
|
|
@keyframes scanner-shake {
|
|
20% {
|
|
transform: translateX(-4px);
|
|
}
|
|
50% {
|
|
transform: translateX(4px);
|
|
}
|
|
80% {
|
|
transform: translateX(-2px);
|
|
}
|
|
}
|
|
|
|
.scanner-app .match-mode {
|
|
flex-shrink: 0;
|
|
filter: drop-shadow(0 1px 2px rgb(0 0 0 / 0.5));
|
|
}
|
|
|
|
.scanner-app .match-headline {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--s-1);
|
|
/* claims row space so the score/chips can never crush the stage name
|
|
out of view in a narrow feed column; wraps instead */
|
|
flex: 1 1 0;
|
|
min-width: 150px;
|
|
}
|
|
|
|
.scanner-app .match-title {
|
|
display: flex;
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
gap: var(--s-1) var(--s-2);
|
|
}
|
|
|
|
.scanner-app .match-stage {
|
|
font-size: var(--font-sm);
|
|
font-weight: var(--weight-extra);
|
|
line-height: 1.1;
|
|
}
|
|
|
|
.scanner-app .match-meta {
|
|
font-size: var(--font-2xs);
|
|
font-weight: var(--weight-semi);
|
|
color: var(--color-text-high);
|
|
}
|
|
|
|
.scanner-app .match-weapons {
|
|
display: flex;
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
gap: var(--s-0-5);
|
|
|
|
/* a team is one wrapping unit: the eight never break up mid-team */
|
|
& .weapon-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--s-0-5);
|
|
}
|
|
|
|
& .vs {
|
|
font-size: var(--font-2xs);
|
|
font-weight: var(--weight-semi);
|
|
color: var(--color-text-high);
|
|
margin: 0 5px;
|
|
}
|
|
|
|
& img.weapon-icon {
|
|
flex-shrink: 0;
|
|
width: 32px;
|
|
height: 32px;
|
|
border-radius: var(--radius-full);
|
|
padding: var(--s-0-5);
|
|
|
|
&.pov {
|
|
outline: var(--border-style-high);
|
|
background-color: var(--color-bg-high);
|
|
outline-offset: 1px;
|
|
}
|
|
}
|
|
}
|
|
|
|
.scanner-app .match-side {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: flex-end;
|
|
gap: var(--s-1-5);
|
|
margin-inline-start: auto;
|
|
align-self: stretch;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.scanner-app .match-score {
|
|
font-weight: var(--weight-extra);
|
|
font-variant-numeric: tabular-nums;
|
|
line-height: 1;
|
|
padding: 5px var(--s-3);
|
|
border-radius: var(--radius-full);
|
|
background: color-mix(in oklab, var(--color-bg) 72%, transparent);
|
|
backdrop-filter: blur(8px);
|
|
|
|
& .win {
|
|
color: var(--color-error-high);
|
|
}
|
|
|
|
& .lose {
|
|
color: var(--color-info-high);
|
|
}
|
|
}
|
|
|
|
.scanner-app .match-chip {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: var(--s-1-5);
|
|
padding: var(--s-0-5) var(--s-2-5);
|
|
border-radius: var(--radius-full);
|
|
font-size: var(--font-2xs);
|
|
font-weight: var(--weight-bold);
|
|
border: var(--border-style);
|
|
background-color: var(--color-bg);
|
|
color: var(--color-text-high);
|
|
white-space: nowrap;
|
|
|
|
& .dot {
|
|
width: 7px;
|
|
height: 7px;
|
|
border-radius: var(--radius-full);
|
|
background: currentColor;
|
|
}
|
|
|
|
&.live {
|
|
color: var(--color-error);
|
|
border-color: var(--color-error-low);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.08em;
|
|
|
|
& .dot {
|
|
animation: scanner-pulse 1.4s ease-in-out infinite;
|
|
}
|
|
}
|
|
|
|
&.in-progress .dot {
|
|
animation: scanner-pulse 1.4s ease-in-out infinite;
|
|
}
|
|
|
|
&.queued,
|
|
&.sending {
|
|
color: var(--color-info-high);
|
|
border-color: var(--color-info-low);
|
|
background-color: var(--color-info-low);
|
|
|
|
& .dot {
|
|
animation: scanner-pulse 1.4s ease-in-out infinite;
|
|
}
|
|
}
|
|
|
|
&.sent {
|
|
color: var(--color-success-high);
|
|
border-color: var(--color-success-low);
|
|
background-color: var(--color-success-low);
|
|
|
|
& a {
|
|
color: inherit;
|
|
text-decoration: underline;
|
|
text-underline-offset: 2px;
|
|
|
|
&:hover {
|
|
color: var(--color-text-high);
|
|
}
|
|
}
|
|
}
|
|
|
|
&.unlinked {
|
|
color: var(--color-warning-high);
|
|
border-color: var(--color-warning-low);
|
|
background-color: var(--color-warning-low);
|
|
}
|
|
|
|
&.failed {
|
|
color: var(--color-error-high);
|
|
border-color: var(--color-error-low);
|
|
background-color: var(--color-error-low);
|
|
}
|
|
}
|
|
|
|
@keyframes scanner-pulse {
|
|
50% {
|
|
opacity: 0.25;
|
|
transform: scale(0.8);
|
|
}
|
|
}
|
|
|
|
/* undo the scanner-wide solid button look for the banner icon button */
|
|
.scanner-app button.match-expand {
|
|
flex-shrink: 0;
|
|
margin-block-start: auto;
|
|
width: 28px;
|
|
height: 28px;
|
|
padding: 0;
|
|
border: none;
|
|
border-radius: var(--radius-full);
|
|
background: color-mix(in oklab, var(--color-bg) 55%, transparent);
|
|
backdrop-filter: blur(8px);
|
|
color: var(--color-text-high);
|
|
transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1);
|
|
|
|
& svg {
|
|
width: var(--field-size-icon);
|
|
height: var(--field-size-icon);
|
|
}
|
|
|
|
&:hover {
|
|
color: var(--color-text);
|
|
background: color-mix(in oklab, var(--color-bg) 78%, transparent);
|
|
}
|
|
|
|
&.expanded {
|
|
transform: rotate(180deg);
|
|
}
|
|
}
|
|
|
|
/* the frame preview: an unstyled button so the image itself is the target */
|
|
.scanner-app .card .meta button.thumb-button {
|
|
height: auto;
|
|
padding: 0;
|
|
border: none;
|
|
background: none;
|
|
margin-inline-start: auto;
|
|
border-radius: var(--radius-field);
|
|
|
|
&:hover {
|
|
filter: brightness(1.2);
|
|
}
|
|
}
|
|
|
|
.scanner-app .match-error {
|
|
padding: 0 var(--s-4) var(--s-2-5);
|
|
font-size: var(--font-2xs);
|
|
color: var(--color-error);
|
|
}
|
|
|
|
.scanner-app .match-card-group {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--s-2);
|
|
}
|
|
|
|
/* source event cards, nested under their match card via an indent rail */
|
|
.scanner-app .match-events {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--s-2);
|
|
margin-inline-start: var(--s-2-5);
|
|
padding-inline-start: var(--s-3);
|
|
border-inline-start: var(--border-width) solid var(--color-border);
|
|
animation: scanner-detail-in 0.25s ease both;
|
|
|
|
& .card {
|
|
padding: var(--s-2-5) 14px;
|
|
min-width: 0;
|
|
}
|
|
}
|
|
|
|
@keyframes scanner-detail-in {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(-4px);
|
|
}
|
|
}
|
|
|
|
.scanner-app .events-summary {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--s-2-5);
|
|
flex-wrap: wrap;
|
|
padding: var(--s-0-5) var(--s-1);
|
|
font-size: var(--font-2xs);
|
|
font-weight: var(--weight-semi);
|
|
color: var(--color-text-high);
|
|
}
|
|
|
|
.scanner-app .events-summary-type {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 5px;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
.scanner-app .events-summary-icon {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 22px;
|
|
height: 22px;
|
|
border-radius: var(--radius-full);
|
|
background: var(--color-bg-higher);
|
|
color: var(--color-text-high);
|
|
}
|
|
|
|
.scanner-app button.events-toggle {
|
|
border: none;
|
|
background: transparent;
|
|
color: var(--color-text-accent);
|
|
height: auto;
|
|
padding: 0;
|
|
font-size: var(--font-2xs);
|
|
}
|
|
|
|
.scanner-app .status.watching::before {
|
|
content: "";
|
|
display: inline-block;
|
|
width: 7px;
|
|
height: 7px;
|
|
border-radius: var(--radius-full);
|
|
background: currentColor;
|
|
margin-inline-end: var(--s-1-5);
|
|
animation: scanner-pulse 1.6s ease-in-out infinite;
|
|
}
|
|
|
|
/* the frame dialog portals outside .scanner-app, so these stay unscoped */
|
|
.scanner-frame-dialog {
|
|
width: max-content;
|
|
max-width: min(96vw, 1400px);
|
|
}
|
|
|
|
.scanner-frame-dialog img.frame-full {
|
|
display: block;
|
|
max-width: 100%;
|
|
max-height: calc(80dvh - 9rem);
|
|
border-radius: var(--radius-field);
|
|
}
|
|
|
|
.scanner-frame-dialog .frame-actions {
|
|
display: flex;
|
|
justify-content: center;
|
|
gap: var(--s-3);
|
|
margin-block-start: var(--s-3);
|
|
}
|
|
|
|
.scanner-app .telemetry {
|
|
margin: var(--s-2) 0;
|
|
font-size: var(--font-2xs);
|
|
color: var(--color-text-high);
|
|
}
|
|
|
|
.scanner-app .telemetry summary {
|
|
cursor: pointer;
|
|
}
|
|
|
|
.scanner-app .telemetry table {
|
|
margin-top: var(--s-1-5);
|
|
border-collapse: collapse;
|
|
}
|
|
|
|
.scanner-app .telemetry table th,
|
|
.scanner-app .telemetry table td {
|
|
padding: var(--s-0-5) var(--s-2-5) var(--s-0-5) 0;
|
|
text-align: right;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
.scanner-app .telemetry table th:first-child,
|
|
.scanner-app .telemetry table td:first-child {
|
|
text-align: left;
|
|
}
|
|
|
|
/* ── narrow viewports ──────────────────────────────────────────────── */
|
|
|
|
/* a 640px wide preview would starve the feed next to it: split evenly instead */
|
|
@container scanner (width < 1000px) {
|
|
.scanner-app .live-layout {
|
|
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
|
}
|
|
|
|
/* the controls wrap here, where a trigger pushed to the end reads as stray */
|
|
.scanner-app button.icon-menu {
|
|
margin-inline-start: 0;
|
|
}
|
|
}
|
|
|
|
/* the two teams' weapons no longer fit beside each other: one row each */
|
|
@container match-card (width < 460px) {
|
|
.scanner-app .match-weapons {
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
row-gap: var(--s-1);
|
|
|
|
& .vs {
|
|
display: none;
|
|
}
|
|
}
|
|
}
|
|
|
|
@container scanner (width < 700px) {
|
|
.scanner-app .live-layout {
|
|
grid-template-columns: minmax(0, 1fr);
|
|
}
|
|
|
|
/* stacked, the preview would otherwise push the feed off the screen */
|
|
.scanner-app video.preview,
|
|
.scanner-app canvas.preview {
|
|
max-height: 45vh;
|
|
object-fit: contain;
|
|
}
|
|
|
|
.scanner-app .vod-item {
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
/* the ROI crops set the width; scroll them instead of the whole page */
|
|
.scanner-app table.inspector {
|
|
display: block;
|
|
overflow-x: auto;
|
|
}
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.scanner-app .match-card,
|
|
.scanner-app .match-card.sending::after,
|
|
.scanner-app .match-card.flash-sent::after,
|
|
.scanner-app .match-card.flash-failed .match-card-main,
|
|
.scanner-app .match-events,
|
|
.scanner-app .match-chip.live .dot,
|
|
.scanner-app .match-chip.in-progress .dot,
|
|
.scanner-app .match-chip.queued .dot,
|
|
.scanner-app .match-chip.sending .dot,
|
|
.scanner-app .status.watching::before {
|
|
animation: none;
|
|
}
|
|
}
|