Files
ontime/apps/client/src/views/countdown/Countdown.scss
T
Claude bc9ae1f1a7 fix(countdown): stop the pinned group header clipping its own outline
The sticky header was offset by minus the row margin, on the assumption
that sticky insets constrain the margin box. They do not, so the header
sat 2px above the scroll port and lost its top 2px to the overflow. That
is exactly the width of the outline marking a group as active, so the
top edge of the ring disappeared as soon as the header pinned.

Anchoring at 0 leaves the header flush with no clipping. The follow
offset now adds a full row gap rather than a single margin, so the
running event lines up under the header on the same rhythm as every
other row.

The operator keeps its negative inset: its list carries a top padding
which the header has to cover, and there is no clipping to avoid since
the offset only cancels that padding.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GjX7D56AW8cL3FXjvtnjtL
2026-08-26 13:01:32 +00:00

341 lines
7.9 KiB
SCSS

@use '@/theme/viewerDefs' as *;
$item-height: 3.5rem;
.countdown {
margin: 0;
box-sizing: border-box; /* reset */
overflow: hidden;
width: 100%; /* restrict the page width to viewport */
height: 100vh;
font-family: var(--font-family-override, $viewer-font-family);
background: var(--background-color-override, $viewer-background-color);
color: var(--color-override, $viewer-color);
display: flex;
flex-direction: column;
gap: $view-element-gap;
padding: $view-outer-padding;
font-size: $base-font-size;
/* =================== HEADER + EXTRAS ===================*/
.project-header {
font-size: $header-font-size;
font-weight: 600;
display: flex;
align-items: center;
gap: 1rem;
}
.project-header__brand {
display: flex;
align-items: center;
gap: 1rem;
min-width: 0;
}
.project-header__brand--without-logo {
align-self: baseline;
}
.logo {
max-width: min(200px, 20vw);
}
.title {
line-height: 1.1em;
}
.clock-container {
margin-left: auto;
font-weight: 600;
.label {
font-size: $timer-label-size;
color: var(--label-color-override, $viewer-label-color);
text-transform: uppercase;
}
.time {
font-size: $timer-value-size;
color: var(--secondary-color-override, $viewer-secondary-color);
letter-spacing: 0.05em;
line-height: 0.95em;
}
}
.fab-container {
position: fixed;
bottom: 1.5rem;
bottom: calc(1.5rem + env(safe-area-inset-bottom));
right: $view-inline-padding;
display: flex;
align-items: center;
justify-content: end;
gap: 2rem;
transition-property: opacity;
transition-duration: 0.3s;
}
.fab-container--hidden {
opacity: 0;
pointer-events: none;
}
/* ========================= LIST ========================*/
.empty-state {
min-height: min(56vh, 36rem);
padding-top: clamp(4rem, 14vh, 9rem);
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
gap: 1.5rem;
}
.list-container {
display: flex;
flex-direction: column;
overflow-y: auto;
padding-bottom: max(8rem, calc(5rem + env(safe-area-inset-bottom)));
}
/* a subscribed group and its subscribed events, bounds the sticky header to its own section.
flex rather than block so that the row margins do not collapse, keeping the spacing between
rows of a section equal to the spacing between sections */
.sub-section {
display: flex;
flex-direction: column;
}
/* scoped to the section, the select view shows the same cards in a flat list */
.sub-section .sub--group {
position: sticky;
top: 0;
z-index: 1;
}
/* ====================== LIST-ITEM ======================*/
.sub {
margin: 2px;
flex: 1;
display: grid;
grid-template-columns: 1rem 1fr auto;
grid-template-areas:
'binder schedule status'
'binder title timer'
'binder secondary secondary';
column-gap: 1rem;
background-color: var(--card-background-color-override, $viewer-card-bg-color);
padding-right: 1rem;
border: 1px solid transparent;
border-radius: $element-border-radius;
&:hover {
.sub__label {
color: $ui-white;
}
}
}
.sub--selected {
background: $blue-700;
box-shadow: inset 0 0 0 1px $blue-500;
}
.sub--live {
background-color: $active-green;
box-shadow: inset 0 0 0 1px color-mix(in srgb, $active-green 75%, $ui-white);
}
.sub--armed {
background-color: $gray-1325;
}
.sub--in-group {
position: relative;
margin-left: 1rem;
&::before {
content: '';
position: absolute;
left: -0.5rem;
top: -0.25rem;
bottom: -0.25rem;
width: 2px;
border-radius: 1px;
background: var(--group-color, transparent);
}
.sub__binder {
box-shadow: inset 3px 0 0 var(--group-color, transparent);
}
}
.sub__binder {
background: var(--user-color, var(--card-background-color-override, $viewer-card-bg-color));
border-radius: $element-border-radius 0 0 $element-border-radius;
grid-area: binder;
}
.sub__schedule {
grid-area: schedule;
padding-top: 0.5rem;
display: flex;
gap: 0.25em;
font-size: $timer-label-size;
color: var(--label-color-override, $viewer-label-color);
white-space: nowrap;
font-variant-numeric: tabular-nums;
}
.sub__schedule--delayed {
color: $ontime-delay-text;
}
.sub__schedule--strike {
text-decoration: line-through;
}
.sub__schedule--over {
color: $playback-over;
}
.sub__schedule--under {
color: $playback-under;
}
.sub--group {
box-shadow: inset 0 0 0 1px var(--user-color, $gray-1325);
/* the card colour is translucent, an opaque base keeps the rows from showing through the sticky header.
split into longhands since the shorthand only takes a colour in its last layer */
background-color: var(--background-color-override, $viewer-background-color);
background-image:
linear-gradient(90deg, color-mix(in srgb, var(--user-color, transparent) 18%, transparent), transparent 42%),
linear-gradient(
var(--card-background-color-override, $viewer-card-bg-color),
var(--card-background-color-override, $viewer-card-bg-color)
);
.sub__binder {
background: var(--user-color, var(--card-background-color-override, $viewer-card-bg-color));
box-shadow: none;
}
.sub__title {
font-weight: 700;
}
.sub__schedule,
.sub__status,
.sub__label {
color: var(--secondary-color-override, $viewer-secondary-color);
}
}
.sub--group.sub--selected {
box-shadow: inset 0 0 0 1px $blue-500;
background:
linear-gradient(90deg, color-mix(in srgb, var(--user-color, transparent) 22%, transparent), transparent 42%),
$blue-700;
.sub__schedule,
.sub__status,
.sub__label,
.sub__eyebrow {
color: $ui-white;
}
}
/* a group holding the running event is only outlined, the green fill belongs to the event row itself */
.sub--group.sub--live {
box-shadow: inset 0 0 0 2px $active-green;
}
/* loaded but not yet running, subtle enough to not compete with the live ring */
.sub--group.sub--armed {
box-shadow: inset 0 0 0 2px $gray-1000;
}
.sub__title {
grid-area: title;
padding-bottom: 0.5rem;
min-width: 0;
font-size: $title-font-size;
line-height: 1.1em;
overflow-wrap: anywhere;
}
.sub__eyebrow {
display: block;
margin-bottom: 0.15rem;
color: var(--secondary-color-override, $viewer-secondary-color);
font-size: $timer-label-size;
font-weight: 700;
line-height: 1;
text-transform: uppercase;
}
.sub__secondary {
grid-area: secondary;
padding-bottom: 0.5rem;
min-width: 0;
font-size: $base-font-size;
line-height: 1.1em;
color: var(--label-color-override, $viewer-label-color);
// allow multi-line text but trim before
white-space: pre-line;
overflow-wrap: anywhere;
}
.sub__status {
grid-area: status;
padding-top: 0.5rem;
font-size: $timer-label-size;
text-align: right;
text-transform: uppercase;
white-space: nowrap;
}
.sub__label {
grid-area: status;
padding-top: 0.5rem;
font-size: $timer-label-size;
color: var(--label-color-override, $viewer-label-color);
text-align: right;
white-space: nowrap;
}
.sub__timer {
grid-area: timer;
font-size: $timer-value-size;
line-height: 1.1em;
font-weight: 600;
text-align: right;
white-space: nowrap;
font-variant-numeric: tabular-nums;
}
.selection-count {
color: var(--label-color-override, $viewer-label-color);
font-size: $timer-label-size;
font-weight: 600;
text-transform: uppercase;
white-space: nowrap;
}
/* ====================== MODIFIERS ======================*/
.subdued {
opacity: 0.6;
}
}