@font-face {
    font-family: SevenSegment;
    font-style: normal;
    font-display: swap;
    src: url(/src/fonts/SevenSegment.otf) format("opentype");
}

/* Column, not row: #loading, #errorMessage and #loadedPresence are meant to be
   mutually exclusive states, but the collapsed card is only 0 tall, never 0
   wide, so as a row item its width:100% basis still claimed half the row and
   shoved the error card off-centre. */
#presenceCard {
    display: flex;
    flex-direction: column;
    font-family: Jost, sans-serif;
    color: white;
    margin: auto;
    align-items: center;
    justify-content: center;
}

/* Collapsed to a 0fr grid row rather than display:none, so the card keeps its
   layout while closed and can be measured. #loadedPresenceInner is the single
   row; with more than one child the fr tracks would size equally and double
   the card's height. There are no transitions here: opening and closing are
   animated by slideCard() in presence.js, which moves the page below with
   transforms instead of re-laying it out on every frame. */
#loadedPresence {
    display: grid;
    grid-template-rows: 0fr;
    overflow: hidden;
    margin-top: 0;
    opacity: 0;
    visibility: hidden;
}

#loadedPresence.showActivity {
    grid-template-rows: 1fr;
    margin-top: 12px;
    opacity: 1;
    visibility: visible;
}

/* Top-anchored, with nothing allowed to shrink, so while the row is opening
   the card keeps its full layout and is simply uncovered from the top. With
   centring and shrinking, everything inside was squashed to fit the row and
   stretched back out as it grew - laid out again every frame, with the text
   creeping 170px down the card and snapping to whole pixels as it went. */
#loadedPresenceInner {
    min-height: 0;
    min-width: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-start;
    text-align: center;
}

#loadedPresenceInner > * {
    flex-shrink: 0;
}

/* First open on page load: the contents come into focus. When the room for
   the card was already held (see reservePresenceSpace() in settings.js) that
   is all that happens; otherwise slideCard() opens it as well. */
#loadedPresence.revealing #loadedPresenceInner {
    animation: presence-focus-in .32s cubic-bezier(.2, .7, .3, 1) both;
}

@keyframes presence-focus-in {
    from {
        opacity: 0;
        filter: blur(10px);
        transform: scale(.94);
    }

    to {
        opacity: 1;
        filter: blur(0);
        transform: scale(1);
    }
}

#loadedPresence>p,
#activities {
    display: inline;
    text-align: center;
    color: rgb(188, 188, 188);
}

/* Pointer events stay on so the hover title still shows. Dragging is off, but
   the context menu (and iOS long-press) is left alone on purpose. */
img.albumArt {
    -webkit-user-drag: none;
    user-select: none;
}

.presenceWrapper,
#presenceCard,
#loadedPresence,
#activityScroller {
    width: 100%;
    max-width: 100%;
    min-width: 0;
}

/* A container, so a card can be sized against the room the strip really has
   (see #amActivity.lastPlayed) - inside the strip's grid a percentage can't. */
#activityScroller {
    position: relative;
    width: 100%;
    isolation: isolate;
    container-type: inline-size;
}

/* Two rows, filled a column at a time: the music card takes both, and the
   programs after it stack two to a column, so two short cards stand as tall
   as the music card beside them rather than each being stretched to it. */
div#activityStrip {
    display: grid;
    grid-auto-flow: column;
    grid-template-rows: auto auto;
    justify-content: safe center;
    align-items: center;
    max-width: 100%;
    overflow-x: auto;
    scrollbar-width: none;
}

#amActivity {
    grid-row: span 2;
}

/* The two rows share the music card's height, so a pair of short cards would
   each sit in the middle of its half with a gap between them. Instead the top
   one sits at the bottom of its row and the bottom one at the top of its, and
   the pair stays together, centred beside the music card. */
.otherActivity:nth-child(odd of .otherActivity) {
    align-self: end;
}

.otherActivity:nth-child(even of .otherActivity) {
    align-self: start;
}

/* The odd one out, in a column of its own - see updateOtherCards() in
   presence.js. Centred beside the music card at its own height. */
.otherActivity.alone {
    grid-row: span 2;
    align-self: center;
}

div#activityStrip::-webkit-scrollbar {
    display: none;
}

.button.activityNav {
    position: absolute;
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    padding: 0;
    border-radius: 50%;
    top: 0;
    bottom: 0;
    margin-block: auto;
}

.button.activityNav.hide {
    display: none;
}

.button.activityNav > i {
    width: auto;
    margin-right: 0;
}

#activityScrollPrev {
    left: 0;
}

#activityScrollNext {
    right: 0;
}

.otherActivity,
#amActivity,
#lyricsCard {
    justify-content: center;
    align-items: center;
    padding: 8px;
    border-radius: 12px;
    margin: 12px;
    color: #000;
    color: rgb(88, 88, 88);
    color: var(--project-color);
    text-align: center;
    transition: width 0.2s;
}

.otherActivity,
#amActivity,
#lyricsCard {
    min-width: 15vw;
    word-wrap: break-word;
    min-width: 250px;
    max-width: 350px;
    justify-content: space-between;
}

.activityLogoLarge {
    border-radius: 5px;
    margin: 12px;
    width: auto;
    height: 96px;
    max-width: 100%;
    object-fit: contain;
}

#amActivityLogoLarge {
  border-radius: 50%;
  margin: 12px;
  animation: spin 2.5s linear infinite;
  will-change: transform;
  backface-visibility: hidden;
  transform: translateZ(0);
}

@keyframes spin {
  to {
    transform: translateZ(0) rotate(360deg);
  }
}

/* Until the first cover is in, the image is a transparent 1px GIF
   (index.html) showing the skeleton shine from styles.css behind it - a
   blank src, unlike none at all, never shows the alt text or a broken-image
   icon. presence.js never puts it back once a cover has loaded, so this is
   only ever the first load.

   The shine takes the place of the spin, and a still placeholder keeps the
   cover's own shape rather than the bars' 6px corners. The last-played
   square's 8px wins over these on its own. */
#amActivityLogoLarge[src^="data:"] {
  border-radius: 50%;
  transform: none;
}

.activityLogoLarge[src^="data:"] {
  border-radius: 5px;
  width: 96px;
}

#activityLogoSmall,
#amActivityLogoSmall {
    border-radius: 25%;
    width: 32px;
    height: 32px;
    position: absolute;
}
div#loading>div {
    display: flex;
    align-items: center;
    justify-content: center;
}

.lds-ring {
    display: inline-block;
    position: relative;
    width: 40px;
    height: 40px;
}

.lds-ring div {
    box-sizing: border-box;
    display: block;
    position: absolute;
    width: 32px;
    height: 32px;
    margin: 4px;
    border: 4px solid #fff;
    border-radius: 50%;
    animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
    border-color: rgba(128, 128, 128, 0.8);
    border-color: var(--accent-color) transparent transparent transparent;
}

.lds-ring div:nth-child(1) {
    animation-delay: -0.45s;
}

.lds-ring div:nth-child(2) {
    animation-delay: -0.3s;
}

.lds-ring div:nth-child(3) {
    animation-delay: -0.15s;
}

html.reduce-motion .lds-ring div {
    display: none;
}

html.reduce-motion .lds-ring::before {
    content: "\f252";
    font-family: "Font Awesome 7 Free";
    font-weight: 900;
    font-size: 24px;
    line-height: 1;
    color: var(--accent-text);
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
}

html.reduce-motion .lds-ring.smallLoader::before {
    font-size: 0.9em;
}

.lds-ring.smallLoader {
    display: none;
    position: relative;
    width: 1em;
    height: 1em;
}
.lds-ring.smallLoader div {
    box-sizing: border-box;
    display: block;
    position: absolute;
    width: 0.8em;
    height: 0.8em;
    margin: 0.1em;
    border: 0.1em solid transparent;
    border-radius: 50%;
    animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
    border-color: var(--accent-color, rgba(128, 128, 128, 0.8)) transparent transparent transparent;
}

.lds-ring.smallLoader div:nth-child(1) {
    animation-delay: -0.45s;
}

.lds-ring.smallLoader div:nth-child(2) {
    animation-delay: -0.30s;
}

.lds-ring.smallLoader div:nth-child(3) {
    animation-delay: -0.15s;
}

.activityName,
.activityState,
.activityDetails,
#amActivityName,
#amActivityDetails,
#amActivityState {
    color: var(--paragraph-color);
    margin-top: 6px;
    margin-right: 6px;
    word-break: break-word;
    word-wrap: break-word;
    overflow-wrap: break-word;
}

.activityName,
#amActivityName {
    color: var(--paragraph-color);
}

.activityDetails,
#amActivityDetails {
    color: #777;
    color: var(--subtitle-color);
}

#timeremaning>p,
#amTimeremaning>p {
    text-align: inherit;
    color: var(--paragraph-color);
}

#activityTime,
.activityTime,
#amActivityTime {
    display: inline;
    color: #000;
    color: rgb(88, 88, 88);
    color: var(--paragraph-color);
    font-family: SevenSegment, monospace, Jost, sans-serif;
}

#errorMessage {
    display: none;
    flex-direction: column;
    margin: 12px;
    padding: 12px;
}

.errorMessage {
    color: #777;
    color: var(--subtitle-color);
}

@keyframes lds-ring {
    0% {
        transform: rotate(0deg);
    }

    100% {
        transform: rotate(360deg);
    }
}

span.titleActivity {
    text-align: left;
    width: 100%;
    margin-bottom: 12px;
    color: var(--paragraph-color);
    display: flex;
    align-items: center;
    justify-content: space-between;
}

.titleActivity > .activityHeading {
    text-align: left;
    min-width: 0;
}

.titleActivity > .activityHeading > i {
    margin-right: 0;
}

.titleActivity h3 {
    display: inline !important;
    margin-left: 0.2em;
    font-weight: normal !important;
}

.otherActivity,
#amActivity {
    display: none;
    flex-direction: column;
    justify-content: center;
    align-items: start;
    text-align: left;
    margin: 12px;
    padding: 12px;
    border-radius: 12px;
    background-color: var(--card-background-color);
    width: -webkit-min-content;
    width: min-content;
}

.style-liquid-glass .otherActivity, .style-liquid-glass #amActivity {
    background-color: var(--liquid-glass-button-background);
border: 1px solid var(--liquid-glass-button-border);
}

#lyricsCard {
    flex-direction: column;
    justify-content: center;
    align-items: start;
    text-align: left;
    margin: 12px;
    padding: 12px;
    border-radius: 12px;
    background-color: var(--card-background-color);
}

#amActivityImages {
    display: flex;
    flex-direction: row;
    justify-content: center;
    align-items: center;
    text-align: center;
    width: 100%;
}

.progress-bar-container {
    position: relative;
    width: 100%;
    height: 5px;
    background-color: var(--card-background-color);
    margin-top: 0.5em;
    margin-bottom: 0.5em;
    border-radius: 5px;
    overflow: hidden;
}

.progress-bar {
    height: 100%;
    background-color: #76c7c0;
    width: 0%;
    transition: width 0.5s;
}
.otherActivityInfo,
#amActivityInfo {
    width: 100%;
    transition: width 0.2s;
        width: 100%;
    transition: width 0.2s;
    flex: 1;
}

/* A program, laid out like the last-played card below: the heading on its
   own line with the clock at its other end, then the icon beside what it is
   doing. Programs often say very little - a name and nothing else - and a
   card that size has nothing to fill a tall one with. */
.otherActivity {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    column-gap: 12px;
    width: 300px;
    max-width: calc(100vw - 56px);
    min-width: 0;
}

.otherActivity .titleActivity {
    flex-basis: 100%;
    margin-bottom: 0;
    gap: 12px;
}

.otherActivity .activityHeading {
    display: flex;
    align-items: center;
    min-width: 0;
}

.otherActivity .activityName {
    margin-top: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

.otherActivity .activityClock {
    flex: none;
    margin: 0 0 0 auto;
    font-size: 0.9em;
    white-space: nowrap;
}

.otherActivity .activityClock.hide {
    display: none;
}

/* Spacing for the row under the heading. Only there when there is a row:
   a card with a name and nothing else is just the heading. */
.otherActivity .activityLogoLarge,
.otherActivity .otherActivityInfo {
    margin-top: 10px;
}

.otherActivity .activityLogoLarge {
    flex: none;
    width: 64px;
    height: 64px;
    margin-left: 0;
    margin-right: 0;
    margin-bottom: 0;
    border-radius: 8px;
    object-fit: cover;
}

.otherActivity .otherActivityInfo {
    flex: 1;
    min-width: 0;
    width: auto;
}

.otherActivity .otherActivityInfo:empty,
.otherActivity .otherActivityInfo.hide {
    display: none;
}

.otherActivity .activityDetails,
.otherActivity .activityState {
    margin-top: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

.otherActivity .activityProgressTrack {
    flex-basis: 100%;
    margin-top: 10px;
    margin-bottom: 0;
}

/* With nothing playing, the music card shows the last song instead (see
   updateLastPlayedInfo in presence.js): the record stops where it is, the
   progress bar goes, and when it stopped goes on the right of the heading. */
#amLastPlayedAgo {
    display: none;
    margin: 0 0 0 auto;
    padding-left: 12px;
    font-size: 0.9em;
    white-space: nowrap;
}

#amActivity.lastPlayed #amLastPlayedAgo {
    display: block;
}

#amActivity.lastPlayed #amProgressTrack {
    display: none;
}

/* The last song is not the main event the way a playing one is, so it is laid
   out like a row in Recently played instead: wide and short, the cover beside
   the song rather than above it. The heading keeps its own line on top, with
   when it stopped at its other end.

   On a phone it shrinks to the scroller's width, less its 12px margins each
   side. That is 100cqi rather than 100%: the strip's grid
   column is sized from the card, so a percentage max-width was ignored while
   sizing it and the card stayed 440px, wider than the screen. */
#amActivity.lastPlayed {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    column-gap: 12px;
    width: min(440px, 100cqi - 24px);
    max-width: none;
}

#amActivity.lastPlayed .titleActivity {
    flex-basis: 100%;
    margin-bottom: 10px;
}

/* The note is for a song that is on. "Last played" stands on its own. */
#amActivity.lastPlayed .activityHeading > i {
    display: none;
}

#amActivity.lastPlayed .titleActivity h3 {
    margin-left: 0;
}

#amActivity.lastPlayed #amActivityImages {
    flex: none;
    width: auto;
}

/* A still square, the same as the list: a stopped record left at whatever
   angle it was spinning at looks like a mistake. */
#amActivity.lastPlayed #amActivityLogoLarge {
    width: 64px;
    height: 64px;
    margin: 0;
    border-radius: 8px;
    object-fit: cover;
    animation: none;
}

#amActivity.lastPlayed #amActivityInfo {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas:
        "title"
        "artist";
    align-items: center;
    flex: 1;
    min-width: 0;
    width: auto;
}

#amActivity.lastPlayed #amActivityDetails,
#amActivity.lastPlayed #amActivityState {
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

#amActivity.lastPlayed #amActivityDetails {
    grid-area: title;
}

#amActivity.lastPlayed #amActivityState {
    grid-area: artist;
}

/* The badge alone is taller than the rest of the row. Recently played, right
   under the card, links every song there instead. */
#amActivity.lastPlayed #apple-link {
    display: none;
}

div#timeremaning,
div#amTimeremaning {
    display: flex;
    flex-direction: row;
    text-align: center;
    align-items: center;
    justify-content: center;
    padding: 10px;
    flex-grow: 1;
}

div#timeremaning>p,
div#amTimeremaning>p {
    padding: 0.2rem;
    color: var(--paragraph-color);
}

.presenceWrapper {
    background-color: unset;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    text-align: center;
    padding: 12px 0;
    margin: 12px 0;
    min-height: unset !important;
}

/* With no activity to show (and no room being held for one), the card and its
   buttons are all display: none, but the wrapper's padding and margin and the
   section's own margins still left a gap under the banner. Take the section
   out entirely, so the banner's margin and the next section's can collapse
   into the one gap. */
html:not(.presence-reserve) div.section:has(> .presenceWrapper > #presenceCard[style*="none"]):not(:has(#lyricsBtn:not(.hide), #recentBtn:not(.hide), #presenceRefresh:not(.hide))) {
    display: none;
}

/* The whole section rather than just the card, so the Lyrics and Refresh
   buttons under it are inside the held room too. The html prefix outranks the
   !important above. */
html.presence-reserve .presenceWrapper {
    min-height: var(--presence-reserve) !important;
}

/* Arrives with the card on load, so it comes into focus alongside it rather
   than popping in sharp underneath a card that is still blurred. */
.presenceWrapper:has(#loadedPresence.revealing) .cardButtons {
    animation: presence-focus-in .32s cubic-bezier(.2, .7, .3, 1) both;
}

.progress-bar {
    height: 100%;
    background-color: var(--accent-color);
    border-radius: 2px;
    width: 100%;
    transform: scaleX(0);
    transform-origin: left center;
    transition: transform 0.2s linear;
    will-change: transform;
}

html.reduce-motion .progress-bar {
    transition: none;
}

#apple-link{
    display: flex;
    align-items: center;
    justify-content: center;
    margin: auto;
    margin-top: 6px;
    margin-bottom: 3px;
    text-align: center;
    flex-grow: 1;
}

#apple-link img {
    height: 41px;
    width: auto;
}

/* Lyrics and Recently played, side by side under the card. They wrap onto
   two lines on a narrow phone rather than squeezing their labels. */
.cardButtons {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 10px;
    margin-top: 12px;
}

/* Both hidden - nothing playing and nothing played - so no gap is left. */
.cardButtons:not(:has(> .button:not(.hide))) {
    display: none;
}

#recentBtn {
    margin: 0;
    justify-content: center;
    align-items: center;
    display: flex;
}

#recentBtn.hide {
    display: none;
}

#presenceRefresh {
    margin: 12px auto 0;
    justify-content: center;
    align-items: center;
    display: flex;
}

#presenceRefresh.hide {
    display: none;
}

#presenceRefresh[disabled] {
    opacity: 0.6;
    pointer-events: none;
}

#errorMessage{
    align-items: center;
    justify-content: center;
    width: 100%;
}
/* ---------- Placeholders while things load ----------

   The music card's placeholder (the shared look is in styles.css). When the
   last visit ended with something playing, settings.js holds the card's room
   open before anything has loaded (presence-reserve, above). This stands in
   that room until the card arrives - shaped like the Apple Music card, with
   the Lyrics button under it, measured off the real ones.

   It sits over the room rather than in the layout, where the card will be,
   so when the room is let go (slideCard() in presence.js) it fades away
   while the card comes into focus in the same place, with no gap between. */
.presenceWrapper {
    position: relative;
}

.presenceSkeleton {
    position: absolute;
    /* The wrapper's padding, #loadedPresence's margin and the card's own. */
    top: 36px;
    left: 50%;
    translate: -50% 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    pointer-events: none;
    opacity: 0;
    visibility: hidden;
    transition: opacity .3s ease, visibility 0s .3s;
}

html.presence-reserve .presenceSkeleton {
    opacity: 1;
    visibility: visible;
    transition: none;
}

.presenceSkeletonCard {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    width: 250px;
    height: 310px;
    padding: 12px;
    border-radius: 12px;
    background-color: var(--card-background-color);
}

.style-liquid-glass .presenceSkeletonCard {
    background-color: var(--liquid-glass-button-background);
    border: 1px solid var(--liquid-glass-button-border);
}

.presenceSkeleton .skeletonHeading {
    width: 110px;
    height: 16px;
    margin-top: 3px;
}

/* 96px, like the cover: measured mid-spin, the cover's box is wider. */
.presenceSkeleton .skeletonArt {
    align-self: center;
    width: 96px;
    height: 96px;
    margin-top: 28px;
    border-radius: 50%;
}

.presenceSkeleton .skeletonTitle {
    width: 70%;
    height: 15px;
    margin-top: 22px;
}

.presenceSkeleton .skeletonArtist {
    width: 45%;
    height: 13px;
    margin-top: 15px;
}

.presenceSkeleton .skeletonProgress {
    width: 100%;
    height: 5px;
    margin-top: 13px;
}

.presenceSkeleton .skeletonBadge {
    align-self: center;
    width: 141px;
    height: 41px;
    margin-top: 14px;
    border-radius: 8px;
}

.presenceSkeleton .skeletonButtons {
    display: flex;
    justify-content: center;
    gap: 10px;
    margin-top: 24px;
}

.presenceSkeleton .skeletonButton {
    width: 105px;
    height: 45px;
    border-radius: 999px;
}

.presenceSkeleton .skeletonRecentButton {
    width: 173px;
}

/* The last-played card instead (#amActivity.lastPlayed, above): wide
   and short, the heading over a square cover with the song beside it, and
   Recently played under it rather than Lyrics. settings.js picks it when the
   last visit ended on that card. */
.presenceSkeletonLast {
    display: none;
}

html.presence-reserve-last .presenceSkeletonCard {
    display: none;
}

html.presence-reserve-last .presenceSkeletonLast {
    display: grid;
    grid-template-columns: 64px minmax(0, 1fr) auto;
    grid-template-areas:
        "heading heading ago"
        "art lines lines";
    align-items: center;
    column-gap: 12px;
    width: 440px;
    max-width: calc(100vw - 48px);
    height: auto;
    box-sizing: border-box;
}

/* Each bar as tall as the line of text it stands in for, less a little. */
.presenceSkeletonLast .skeletonHeading {
    grid-area: heading;
    width: 90px;
    margin: 3px 0 13px;
}

.presenceSkeletonLast .skeletonAgo {
    grid-area: ago;
    width: 64px;
    height: 13px;
    margin: 4px 0 14px;
    border-radius: 999px;
}

.presenceSkeletonLast .skeletonArt {
    grid-area: art;
    width: 64px;
    height: 64px;
    margin: 0;
    border-radius: 8px;
}

.presenceSkeletonLast .skeletonLines {
    grid-area: lines;
    display: flex;
    flex-direction: column;
}

html.presence-reserve-last .presenceSkeleton .skeletonButtons {
    margin-top: 30px;
}

html.presence-reserve-last .presenceSkeleton .skeletonLyricsButton {
    display: none;
}
.presenceSkeletonLast .skeletonArtist {
    width: 40%;
    margin-top: 16px;
}

/* ---------- Recently played ----------

   The dialog borrows .emailDialog's frame, backdrop and opening animation
   (styles.css), so the two read as the same kind of thing. Only the list is
   its own. */

.recentDialog {
    width: min(460px, calc(100vw - 32px));
}

.recentStatus {
    margin: 12px 0 0;
}

.recentStatus[hidden] {
    display: none;
}

/* Twenty rows do not fit on a phone, so the list scrolls inside the dialog
   and the buttons stay in sight. */
.recentList {
    list-style: none;
    margin: 16px 0 0;
    padding: 0;
    width: 100%;
    max-height: min(420px, 55vh);
    overflow-y: auto;
    overscroll-behavior: contain;
    /* Still scrolls by wheel, swipe and keyboard, just without a bar beside
       the rows. */
    scrollbar-width: none;
    text-align: left;
    /* Fades the rows out at an edge with more past it. recentlyPlayed.js
       sets fadeTop and fadeBottom as the list scrolls, so the first song is
       not dimmed while it is already at the top. */
    --recent-fade-top: 0px;
    --recent-fade-bottom: 0px;
    -webkit-mask-image: linear-gradient(to bottom,
        transparent, #000 var(--recent-fade-top),
        #000 calc(100% - var(--recent-fade-bottom)), transparent);
    mask-image: linear-gradient(to bottom,
        transparent, #000 var(--recent-fade-top),
        #000 calc(100% - var(--recent-fade-bottom)), transparent);
    transition: --recent-fade-top .2s ease, --recent-fade-bottom .2s ease;
}

.recentList.fadeTop {
    --recent-fade-top: 32px;
}

.recentList.fadeBottom {
    --recent-fade-bottom: 32px;
}

.recentList::-webkit-scrollbar {
    display: none;
}

/* Registered so the fades can ease in and out; a browser without @property
   just snaps them. */
@property --recent-fade-top {
    syntax: "<length>";
    inherits: false;
    initial-value: 0px;
}

@property --recent-fade-bottom {
    syntax: "<length>";
    inherits: false;
    initial-value: 0px;
}

html.reduce-motion .recentList {
    transition: none;
}

.recentList:empty {
    display: none;
}

/* .modalButtons has no space of its own above it, and the last row would
   otherwise sit right against the button. */
.recentDialog .modalButtons {
    margin-top: 20px;
}

/* Cover, then title over artist, then the time on the right. */
.recentRow {
    display: grid;
    grid-template-columns: 48px minmax(0, 1fr) auto;
    grid-template-areas:
        "art title when"
        "art artist when";
    align-items: center;
    column-gap: 12px;
    padding: 8px 12px 8px 8px;
}

.recentArt {
    grid-area: art;
    width: 48px;
    height: 48px;
    border-radius: 8px;
    object-fit: cover;
    background-color: var(--liquid-glass-button-background);
}

.recentArtEmpty {
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--icon-color);
}

.recentArtEmpty > i {
    margin: 0;
}

/* Only there to group the two lines for the markup; the grid places them. */
.recentText {
    display: contents;
}

.recentTitle {
    grid-area: title;
    align-self: end;
}

.recentTitle,
.recentArtist {
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

.recentArtist {
    grid-area: artist;
    align-self: start;
    font-size: 0.9em;
}

.recentWhen {
    grid-area: when;
    font-size: 0.85em;
    white-space: nowrap;
}

/* The placeholder rows, while the first list loads. The bars sit about where
   the text's own lines will, and the cover keeps its own rounder corners
   rather than taking the bars' 6px. */
.recentList.skeleton .recentArt {
    border-radius: 8px;
}

.recentSkeletonTitle {
    height: 14px;
    margin-bottom: 4px;
}

.recentSkeletonArtist {
    height: 12px;
    margin-top: 4px;
}

.recentSkeletonWhen {
    width: 64px;
    height: 12px;
}

/* On a phone a time on the right leaves the title a few letters, so it goes
   under the artist instead and the title gets the whole width. */
@media (max-width: 480px) {
    .recentRow {
        grid-template-columns: 48px minmax(0, 1fr);
        grid-template-areas:
            "art title"
            "art artist"
            "art when";
    }

    .recentSkeletonWhen {
        margin-top: 6px;
    }

    .recentTitle {
        align-self: auto;
    }

    .recentArtist {
        align-self: auto;
    }
}

/* Taylor Swift's 13, in the bubble from hovering her name - see
   initArtistEggs() in presence.js. Bold and a bit crooked, like it was drawn on
   in marker, the way she wrote it on her hand. */
.speechBubble.luckyThirteen {
    padding: 0.3rem 0.7rem;
    font-size: 1.1rem;
    font-weight: 700;
    rotate: -6deg;
}
