#app { min-height: 100%; display: flex; flex-direction: column; }
/* The nav floats over the page, so the last row of photos needs room to clear
   it, and the notch needs room at the top now that nothing else holds it. */
main { flex: 1; padding-top: env(safe-area-inset-top); padding-bottom: calc(var(--nav-h) + 8px); }
.lang-list .list-item.selected::after { content: ''; }

/* Grid */
.grid { display: grid; grid-template-columns: repeat(var(--cols, 3), 1fr); gap: 2px; padding: 2px; touch-action: pan-y; user-select: none; -webkit-user-select: none; }
.grid.empty::before { content: attr(data-empty); grid-column: 1 / -1; text-align: center; padding: 48px 24px; color: var(--md-on-surface-variant); }
.tile { position: relative; aspect-ratio: 1; overflow: hidden; background: var(--md-surface-container-high); border-radius: 2px; }
/* Long-press is our selection gesture: keep iOS from opening the image callout. */
.tile, .tile img { -webkit-touch-callout: none; }
.tile img { width: 100%; height: 100%; object-fit: cover; display: block; opacity: 0;
  transition: transform var(--md-dur-med) var(--md-ease), border-radius var(--md-dur-med) var(--md-ease), opacity var(--md-dur-med) linear; }
/* grid.js adds .loaded once the thumbnail has decoded, so it fades in rather
   than popping over the shimmer. */
.tile.loaded img { opacity: 1; }
.tile .badge { position: absolute; right: 4px; bottom: 4px; display: inline-flex; align-items: center; gap: 2px; padding: 2px 6px; border-radius: 10px; background: rgba(0,0,0,.55); color: #fff; font-size: 12px; }
.tile .check { position: absolute; top: 6px; left: 6px; color: var(--md-primary); background: #fff; border-radius: 50%; line-height: 0;
  /* Kept in the layout and hidden, so entering select mode can animate it. */
  visibility: hidden; opacity: 0; transform: scale(.5);
  transition: transform var(--md-dur-med) var(--md-ease-out), opacity var(--md-dur-short) linear, visibility var(--md-dur-short); }
.grid.selecting .tile .check { visibility: visible; opacity: 1; transform: none; color: var(--md-outline); background: rgba(255,255,255,.7); }
.grid.selecting .tile .check svg path { fill: transparent; stroke: currentColor; stroke-width: 1.5; }
.grid.selecting .tile.selected .check { color: var(--md-primary); background: #fff; }
.grid.selecting .tile.selected .check svg path { fill: currentColor; stroke: none; }
.grid.selecting .tile.selected img { transform: scale(.86); border-radius: 8px; }
.sentinel { grid-column: 1 / -1; height: 1px; }
.invalid { max-width: 420px; margin: 15vh auto; padding: 24px; text-align: center; }
.invalid h1 { font-size: 24px; font-weight: 400; }

/* Viewer */
.viewer { position: fixed; inset: 0; z-index: 25; background: rgb(0 0 0 / var(--veil, 1)); color: #fff; }
.viewer-bar { position: absolute; top: 0; left: 0; right: 0; z-index: 2;
  display: flex; align-items: center; height: calc(64px + env(safe-area-inset-top));
  padding: env(safe-area-inset-top) 4px 0;
  /* A scrim rather than a bar: dark enough under the text to read over any photo,
     gone by the bottom edge. */
  background: linear-gradient(to bottom, rgba(0,0,0,.62) 0%, rgba(0,0,0,.38) 60%, rgba(0,0,0,0) 100%);
  transition: opacity var(--md-dur-med) var(--md-ease), transform var(--md-dur-med) var(--md-ease); }
.viewer-bar .icon-btn { color: #fff; }
/* Two lines: the day the shot was taken, then its time and the position in
   the album. */
.viewer-bar .grow { flex: 1; min-width: 0; display: flex; flex-direction: column; justify-content: center;
  text-align: center; line-height: 1.25; padding: 0 4px; }
.viewer-bar .vdate { font-size: 14px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  text-shadow: 0 1px 3px rgba(0,0,0,.55); }
.viewer-bar .vsub { font-size: 12px; opacity: .85; text-shadow: 0 1px 3px rgba(0,0,0,.55); }
.viewer.hide-bar .viewer-bar { opacity: 0; transform: translateY(-8px); pointer-events: none; }
.viewer .stage { position: absolute; inset: 0; overflow: hidden; touch-action: none; }
/* Pinch/double-tap/swipe are ours (touch-action: none above); keep iOS from
   opening the image callout or starting a text selection mid-gesture. */
.viewer .stage img { max-width: 100%; max-height: 100%; object-fit: contain; transform-origin: center; transition: transform .05s linear; -webkit-touch-callout: none; user-select: none; -webkit-user-select: none; }
.viewer .stage video { max-width: 100%; max-height: 100%; width: 100%; background: #000; display: block; }

/* Downloads */
.dl-sheet { padding: 8px 24px calc(24px + env(safe-area-inset-bottom)); }
.dl-sheet h3 { margin: 4px 0 12px; }
.dl-status { margin: 12px 0; color: var(--md-on-surface-variant); min-height: 1.4em; }
.dl-actions { display: flex; justify-content: flex-end; gap: 8px; }

/* Sheets and install banner */
#install-banner .btn { min-height: 48px; padding: 0 16px; }
.list-item .icon-btn { margin-right: -12px; }
/* The row's own button is the tap target: stretch it over the full row height so
   there is no dead strip above and below the label. */
.list-item > .grow { align-self: stretch; min-height: 48px; display: flex; flex-direction: column; justify-content: center; }

/* Admin panel (Italian-only, /admin*) */
.admin { padding: 8px 16px 96px; max-width: 720px; margin: 0 auto; }
.admin.narrow { max-width: 400px; padding-top: 15vh; }
.admin h1 { font-weight: 400; }
.admin h2 { display: flex; align-items: center; gap: 8px; font-size: 16px; font-weight: 500; color: var(--md-on-surface-variant); margin: 24px 0 8px; }
.admin form { display: grid; gap: 12px; }
.admin .stack { display: grid; gap: 8px; }
.admin .row { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 12px; }
.album-card, .client-card { display: flex; align-items: center; gap: 12px; color: inherit; text-decoration: none; }
.client-card { flex-wrap: wrap; }
.admin .grow { flex: 1; min-width: 0; }
.admin .sub { font-size: 14px; color: var(--md-on-surface-variant); }
.admin .sub.url { overflow-wrap: anywhere; opacity: .8; }
.admin .error-text { font-size: 14px; overflow-wrap: anywhere; }
/* The back arrow is the only <a> that wears .icon-btn; kill the UA underline.
   (The bar is a sibling of main.admin, so it cannot be scoped under .admin.) */
.top-bar a.icon-btn { text-decoration: none; }
/* grid.js writes --cols inline from the stored preference; this is the fallback. */
.admin #grid { --cols: 4; }
.link-box { background: var(--md-surface-container-highest); border-radius: 8px; padding: 12px; margin-bottom: 12px; overflow-wrap: anywhere; font-size: 13px; }
.qr { display: block; width: 220px; max-width: 100%; margin: 0 auto 16px; background: #fff; padding: 8px; border-radius: 8px; }
.qr-error { text-align: center; }

/* ---- Grid motion ---- */
/* Tiles arrive in a short cascade; grid.js caps the delay so a full page does
   not spend a second filling in. */
.tile { animation: tile-in var(--md-dur-long) var(--md-ease-out) backwards; animation-delay: var(--tile-delay, 0ms); }
@keyframes tile-in { from { opacity: 0; transform: scale(.92); } }
/* Stands in for the thumbnail until it decodes. */
.tile:not(.loaded)::after { content: ''; position: absolute; inset: 0; transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, rgba(128,128,128,.18), transparent);
  animation: shimmer 1.4s var(--md-ease) infinite; }
@keyframes shimmer { to { transform: translateX(100%); } }
.grid.selecting .tile.selected .check { transform: scale(1.08); }

/* ---- Viewer motion ---- */
.viewer { animation: viewer-in var(--md-dur-long) var(--md-ease-out); }
@keyframes viewer-in { from { opacity: 0; transform: scale(.94); } }
.viewer.out { animation: viewer-out var(--md-dur-med) var(--md-ease-in) forwards; pointer-events: none; }
@keyframes viewer-out { to { opacity: 0; transform: scale(.94); } }
/* One frame per item. A swipe animates the outgoing frame off while its
   replacement comes in from the other side, so two can be on the stage at once.
   viewer.js sets transition:none on the frame under the finger. */
.viewer .frame { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  transition: transform var(--md-dur-long) var(--md-ease); }

@media (prefers-reduced-motion: reduce) {
  /* A 1ms shimmer would strobe; drop it rather than speed it up. */
  .tile:not(.loaded)::after { display: none; }
}

/* ---- Drag to dismiss ---- */
/* The backdrop eases back when a drag is abandoned, but tracks the finger 1:1
   while one is down. */
.viewer { transition: background-color var(--md-dur-med) var(--md-ease); }
.viewer.dragging { transition: none; }
/* The frame follows the finger and shrinks; the black backdrop and every piece
   of chrome over it fade with --veil, which viewer.js drives from the travel. */
.viewer.dragging:not(.hide-bar) .viewer-bar, .viewer.dragging:not(.hide-bar) .vctl,
.viewer.dragging .play-big { opacity: var(--veil, 1); }
/* Committed: the frame keeps going the way it was headed, so the overlay must
   fade in place rather than run its own scale-down exit on top of it. */
.viewer.dismissing.out { animation: fade-out var(--md-dur-long) var(--md-ease-in) forwards; }

/* ---- Video player ---- */
/* Material controls over a native <video>: dropping the native `controls` is
   what frees the bottom of the frame for the swipe gestures. */
.player { position: relative; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; }
.play-big { position: absolute; display: flex; align-items: center; justify-content: center; width: 72px; height: 72px;
  border-radius: 50%; background: rgba(0,0,0,.45); color: #fff; backdrop-filter: blur(2px);
  transition: opacity var(--md-dur-med) var(--md-ease), transform var(--md-dur-med) var(--md-ease-out); }
.play-big:active { transform: scale(.92); }
.player.playing .play-big { opacity: 0; transform: scale(.8); pointer-events: none; }
.play-big .on-ended, .player.ended .play-big .on-paused { display: none; }
.player.ended .play-big .on-ended { display: block; }
.vspin { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; pointer-events: none; }
.vspin .spinner { margin: 0; border-color: rgba(255,255,255,.25); border-top-color: #fff; }

.vctl { position: absolute; left: 0; right: 0; bottom: 0; z-index: 2; display: flex; align-items: center; gap: 4px;
  padding: 8px 8px calc(8px + env(safe-area-inset-bottom));
  background: linear-gradient(to top, rgba(0,0,0,.62) 0%, rgba(0,0,0,.38) 55%, rgba(0,0,0,0) 100%);
  transition: opacity var(--md-dur-med) var(--md-ease), transform var(--md-dur-med) var(--md-ease); }
.vctl .icon-btn { color: #fff; }
.vtime { font-size: 12px; font-variant-numeric: tabular-nums; opacity: .9; min-width: 34px; text-align: center; }
.viewer.hide-bar .vctl { opacity: 0; transform: translateY(8px); pointer-events: none; }

/* The touch target is the full 24px strip; only the 4px track is painted, and it
   grows while a finger is on it. */
.seek { position: relative; flex: 1; height: 24px; display: flex; align-items: center; touch-action: none; cursor: pointer; }
.seek .track { position: relative; width: 100%; height: 4px; border-radius: 2px; background: rgba(255,255,255,.3); overflow: hidden;
  transition: height var(--md-dur-short) var(--md-ease); }
.seek.scrubbing .track { height: 6px; }
.seek .buf, .seek .fill { position: absolute; left: 0; top: 0; bottom: 0; border-radius: 2px; }
.seek .buf { background: rgba(255,255,255,.35); }
.seek .fill { background: #fff; }
.seek .knob { position: absolute; left: var(--knob, 0%); top: 50%; width: 12px; height: 12px; margin: -6px 0 0 -6px;
  border-radius: 50%; background: #fff; pointer-events: none;
  transition: transform var(--md-dur-short) var(--md-ease); }
.seek.scrubbing .knob { transform: scale(1.4); }
.seek:focus-visible { outline: 2px solid #fff; outline-offset: 2px; border-radius: 4px; }

/* ---- Day headings ---- */
/* One per day the album covers, spanning the whole grid and parking at the top
   while its own photos scroll under it. White on a scrim rather than on the app
   surface: the photos pass beneath, so the label has to hold its own over any
   picture, and the gradient is gone by the bottom edge so it reads as a wash
   rather than a bar. */
.grid > .day-head { grid-column: 1 / -1; position: sticky; top: env(safe-area-inset-top); z-index: 5;
  display: flex; align-items: center; margin: 0; padding: 16px 10px 14px;
  font-size: 14px; font-weight: 500; color: #fff; text-shadow: 0 1px 3px rgba(0,0,0,.6);
  background: linear-gradient(to bottom, rgba(0,0,0,.72) 0%, rgba(0,0,0,.5) 55%, rgba(0,0,0,0) 100%); }
.grid.selecting > .day-head { cursor: pointer; }

/* The day's own checkbox. Out of the layout entirely while browsing, so a plain
   scroll through the album is dates and photos and nothing else; it widens into
   place when selection mode opens. */
.day-check { position: relative; display: flex; align-items: center; justify-content: center; flex: none;
  width: 0; height: 28px; margin: 0; border-radius: 50%; overflow: hidden; opacity: 0;
  color: rgba(255,255,255,.92); filter: drop-shadow(0 1px 2px rgba(0,0,0,.5)); pointer-events: none;
  transition: width var(--md-dur-med) var(--md-ease), margin var(--md-dur-med) var(--md-ease),
              opacity var(--md-dur-short) linear, color var(--md-dur-short) linear; }
.grid.selecting .day-check { width: 28px; margin-right: 10px; opacity: 1; pointer-events: auto; }
.day-check svg { transition: transform var(--md-dur-med) var(--md-ease-out); }
.day-check svg path { fill: transparent; stroke: currentColor; stroke-width: 1.5; }
/* Part of the day picked: an empty ring would read as "none", so it carries a dot. */
.day-head.some .day-check { color: #fff; }
.day-head.some .day-check::after { content: ''; position: absolute; width: 11px; height: 11px;
  border-radius: 50%; background: currentColor; }
.day-head.all .day-check { color: #fff; }
.day-head.all .day-check svg { transform: scale(1.1); }
.day-head.all .day-check svg path { fill: currentColor; stroke: none; }
.day-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---- Details sheet ---- */
.info-sheet .row { display: flex; gap: 16px; align-items: baseline; padding: 10px 24px; font-size: 15px; }
.info-sheet .k { color: var(--md-on-surface-variant); flex: none; }
.info-sheet .v { flex: 1; text-align: right; overflow-wrap: anywhere; }
