*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

:root {
  --bg: #ffffff;
  --ink: #0d0d0d;
  --ink-muted: #999999;
  --ink-faint: #e8e8e8;
  --col-w: 280px;
  --tl-w: 96px;
  --row-h: 240px;
  --photo-size: 180px;
  --topbar-h: 52px;
  --fhdr-h: 48px;
  --font: -apple-system, "Helvetica Neue", Helvetica, Arial, sans-serif;
  --font-mono: "SF Mono", "Menlo", "Consolas", monospace;
}

html,
body {
  height: 100%;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font);
  overflow: hidden;
  -webkit-font-smoothing: antialiased;
}

/* ── Topbar ──────────────────────────────────────── */
#topbar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: var(--topbar-h);
  background: var(--bg);
  display: flex;
  align-items: center;
  padding: 0 16px;
  gap: 12px;
  z-index: 300;
  border-bottom: 1px solid var(--ink-faint);
}

.wordmark {
  font-size: 17px;
  font-weight: 500;
  letter-spacing: -0.03em;
  color: var(--ink);
}

.wordmark em {
  font-style: normal;
  font-weight: 300;
}

#live-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--ink);
  opacity: 0.35;
  animation: blink 2.4s ease infinite;
  flex-shrink: 0;
}
#live-dot.live { background: #c0233e; }

@keyframes blink {

  0%,
  100% {
    opacity: 0.35;
  }

  50% {
    opacity: 0.08;
  }
}

#view-toggle {
  background: transparent;
  border: 1px solid var(--ink-faint);
  border-radius: 4px;
  padding: 5px 7px;
  cursor: pointer;
  color: var(--ink-muted);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 0;
  transition: color 0.12s, border-color 0.12s;
}

#view-toggle:hover {
  color: var(--ink);
  border-color: var(--ink-muted);
}

#view-toggle svg {
  width: 14px;
  height: 14px;
  fill: currentColor;
}

/* Each icon shows the *target* view (i.e., the view you'll switch TO). */
body.view-wall #view-icon-wall { display: none; }
body.view-carousel #view-icon-carousel { display: none; }

#date-picker {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.05em;
  color: var(--ink);
  background: transparent;
  border: 1px solid var(--ink-faint);
  border-radius: 4px;
  padding: 4px 8px;
  cursor: pointer;
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
}
#date-picker:hover { border-color: var(--ink-muted); }
#date-picker:focus { outline: none; border-color: var(--ink); }

@media (max-width: 600px) {
  #topbar {
    padding: 0 12px;
    gap: 8px;
  }
}

#empty-state {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  color: var(--ink-muted);
  font-size: 14px;
  text-align: center;
  padding: 32px;
  pointer-events: none;
}

/* ── Main scroll area ───────────────────────────── */
#scroll-wrap {
  position: fixed;
  top: var(--topbar-h);
  left: 0;
  right: 0;
  bottom: 0;
  overflow: scroll;
  /* Suppress iOS rubber-band at the top edge so the custom pull-to-refresh
     gesture isn't fighting the browser's native overscroll. */
  overscroll-behavior-y: contain;
}

/* ── Account banner (shown for paused/stopped users) ─ */
.account-banner {
  position: fixed;
  top: var(--topbar-h);
  left: 0;
  right: 0;
  z-index: 250;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 16px;
  font-size: 13px;
  border-bottom: 1px solid var(--ink-faint);
  background: #fff5d6;
  color: var(--ink);
}
.account-banner.stopped { background: #ffe1e1; }
.account-banner-text { flex: 1; min-width: 0; }
.account-banner-action {
  background: var(--ink);
  color: #fff;
  border: none;
  border-radius: 4px;
  padding: 6px 10px;
  font-size: 12px;
  cursor: pointer;
  white-space: nowrap;
}
.account-banner-action:disabled { opacity: 0.5; cursor: default; }
.account-banner-action:not(:disabled):hover { opacity: 0.85; }
.account-banner-dismiss {
  background: transparent;
  border: none;
  color: var(--ink-muted);
  font-size: 16px;
  cursor: pointer;
  padding: 2px 6px;
  line-height: 1;
}
.account-banner-dismiss:hover { color: var(--ink); }
body.has-account-banner #scroll-wrap { top: calc(var(--topbar-h) + var(--account-banner-h, 44px)); }

@media (max-width: 600px) {
  .account-banner {
    padding: 6px 10px;
    gap: 8px;
    font-size: 12px;
    line-height: 1.3;
  }
  .account-banner-action {
    padding: 5px 8px;
    font-size: 11px;
  }
  .account-banner-dismiss {
    padding: 2px 4px;
    font-size: 14px;
  }
}

#canvas {
  margin: 0 auto;
  position: relative;
}

/* ── Friend header ───────────────────────────────── */
#friend-header {
  position: sticky;
  top: 0;
  z-index: 100;
  background: var(--bg);
  display: flex;
  width: max-content;
  border-bottom: 1px solid var(--ink-faint);
}

.fh-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  height: var(--fhdr-h);
  flex-shrink: 0;
}

.fh-cell.friend-col {
  width: var(--col-w);
}

.fh-cell.tl-col {
  width: var(--tl-w);
}

.fh-name {
  font-size: 16px;
  font-weight: 600;
  color: var(--ink);
  letter-spacing: 0.01em;
}

/* ── Center line ─────────────────────────────────── */
#center-line {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--ink-faint);
  pointer-events: none;
  z-index: 0;
}

/* ── Grid ────────────────────────────────────────── */
#grid {
  position: relative;
  display: flex;
  flex-direction: column;
  z-index: 1;
}

.hour-row {
  display: flex;
  flex-direction: row;
  width: max-content;
  position: relative;
}

/* Subtle dotted line spans the whole row at the hour mark, replacing the
   short central tick. Hidden on the topmost row to avoid doubling the
   friend-header's solid border. */
.hour-line {
  position: absolute;
  top: 18px;
  left: 0;
  right: 0;
  height: 0;
  border-top: 1px dotted var(--ink-faint);
  pointer-events: none;
  z-index: 1;
}

.hour-row:first-child .hour-line {
  display: none;
}

.hour-row.now .hour-line {
  border-top-color: var(--ink);
  opacity: 0.35;
}

/* Hour label is absolutely positioned within the row; JS updates `left`
   on scroll so the label follows the viewport horizontally and the hour
   stays visible even when the central timeline is scrolled off-screen.
   The white background covers the dotted line behind it. */
.hour-label {
  position: absolute;
  top: 18px;
  transform: translate(-50%, -50%);
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.05em;
  color: var(--ink-muted);
  background: var(--bg);
  padding: 4px 11px;
  white-space: nowrap;
  font-weight: 500;
  line-height: 1;
  z-index: 3;
  pointer-events: none;
  will-change: left;
}

.hour-row.now .hour-label {
  color: var(--ink);
  font-weight: 500;
}

.cell {
  width: var(--col-w);
  height: var(--row-h);
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
}

/* Timeline center cell — empty spacer that reserves the gap between the
   left and right halves of the grid. The hour mark (dotted line + label)
   is rendered at row level, not inside this cell. */
.tl-cell {
  width: var(--tl-w);
  height: var(--row-h);
  flex-shrink: 0;
  position: relative;
  z-index: 2;
}

/* ── NOW pill ────────────────────────────────────── */
#now-pill {
  position: absolute;
  top: var(--fhdr-h);
  z-index: 50;
  transform: translateX(-50%);
  background: var(--ink);
  color: var(--bg);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 5px 11px;
  border-radius: 0 0 4px 4px;
  pointer-events: none;
}

/* ── Photo cards ─────────────────────────────────── */
/* Card sizes itself to the image's natural aspect ratio (capped to
   --photo-size on either edge). Each card carries a small per-photo
   rotation via --rot so the wall feels organic; hover snaps it back
   straight and lifts above neighbors. */
.photo-card {
  max-width: var(--photo-size);
  max-height: var(--photo-size);
  background: var(--ink-faint);
  border-radius: 4px;
  cursor: pointer;
  transition: transform 0.22s ease, box-shadow 0.22s ease;
  position: relative;
  z-index: 2;
  /* Tight resting shadow only — the 16px-blur ambient we had originally
     scaled to a 30fps drop with ~20 cards on screen. This single 2px blur
     paints cheaply enough to keep on every card. */
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
  transform: rotate(var(--rot, 0deg));
  display: block;
  line-height: 0;
}

.photo-card:hover {
  transform: rotate(0deg) scale(1.06);
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.18);
  z-index: 20;
}

.photo-card img {
  display: block;
  max-width: var(--photo-size);
  max-height: var(--photo-size);
  width: auto;
  height: auto;
  border-radius: 4px;
  -o-object-fit: contain;
     object-fit: contain;
}

/* Caption band across the bottom of the photo. Dark gradient fades up so
   white text reads cleanly without obscuring the upper portion of the
   image. Spans the full width — the like-strip pins overlap visually on
   the right and the band keeps text legible behind them. Single line with
   ellipsis; full text is in the lightbox. */
.photo-caption {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 2;
  padding: 18px 10px 6px;
  background: linear-gradient(to top,
    rgba(0, 0, 0, 0.72) 0%,
    rgba(0, 0, 0, 0.5) 55%,
    rgba(0, 0, 0, 0) 100%);
  color: #fff;
  font-size: 12px;
  line-height: 1.3;
  pointer-events: none;
  overflow: hidden;
  /* Match the photo-card's bottom corners so the band doesn't bleed past. */
  border-radius: 0 0 4px 4px;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.45);
}

/* Inner text element does the line-clamping. Keeping the clamp here (not on
   the parent) avoids the webkit-box + padding quirk where a third line
   leaks through. */
.photo-caption-text {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  text-overflow: ellipsis;
  word-wrap: break-word;
  overflow-wrap: anywhere;
}

/* Carousel card has bigger rounded corners; match them. */
.h-card .photo-caption {
  border-radius: 0 0 14px 14px;
  font-size: 13px;
  padding: 18px 12px 8px;
  line-height: 1.3;
}

/* Heart pins line the bottom edge of the photo like little stickers.
   Order L→R: [+N badge] [other outlines, oldest → newest] [yours / ghost].
   Strip pokes below the card edge so each pin straddles it. Padding doubles
   as a finger-friendly tap target. */
.like-strip {
  position: absolute;
  right: 4px;
  bottom: -20px;
  display: flex;
  flex-direction: row;
  align-items: center;
  padding: 8px 6px;
  cursor: pointer;
  z-index: 3;
  -webkit-tap-highlight-color: transparent;
}

.like-pin {
  width: 25px;
  height: 27px;
  display: inline-block;
  transform: rotate(var(--pin-tilt, 0deg));
  transition: transform 0.18s ease, opacity 0.2s ease;
  /* filter: drop-shadow(0 1px 1.5px rgba(0, 0, 0, 0.22)); */
}

/* Slight overlap between adjacent pins so they read as a stuck-on cluster.
   Later DOM = higher z-index by default, so newer "others" sit on top of
   older ones, and yours/ghost (last in DOM) layers above everything. */
.like-strip > * + * {
  margin-left: -5px;
}

/* Thick white stroke on every pin gives them the enamel-sticker look.
   paint-order draws the stroke first so the fill covers the inner half —
   what you see outside the heart is a clean ~1.5px white edge. */
.like-pin svg {
  width: 100%;
  height: 100%;
  display: block;
  overflow: visible;
  stroke: #fff;
  stroke-width: 3;
  stroke-linejoin: round;
  stroke-linecap: round;
  paint-order: stroke;
}

/* Yours: bold Instagram-ish red. */
.like-pin.mine svg {
  fill: #ed4956;
}

/* Others: same hue, distinctly lighter so yours is unmistakably the bold one. */
.like-pin.other svg {
  fill: #f49aa6;
}

/* Ghost: dashed outline pin acting as the "tap to like" affordance.
   Hidden by default on the wall (desktop) and only revealed when hovering
   the photo card. The carousel rule below keeps it visible on mobile,
   where there's no hover to reveal it. */
.like-pin.ghost svg {
  fill: rgba(255, 255, 255, 0.55);
  stroke: rgba(13, 13, 13, 0.42);
  stroke-width: 1.7;
  stroke-dasharray: 2.2 1.8;
  paint-order: normal;
}
.like-pin.ghost {
  opacity: 0;
}
.photo-card:hover .like-pin.ghost,
.h-card .like-pin.ghost {
  opacity: 1;
}

.like-strip:hover .like-pin.ghost {
  transform: rotate(var(--pin-tilt, 0deg)) scale(1.14);
}
.like-strip:hover .like-pin.ghost svg {
  fill: rgba(255, 220, 228, 0.85);
  stroke: rgba(237, 73, 86, 0.6);
}
.like-strip:hover .like-pin.mine {
  transform: rotate(var(--pin-tilt, 0deg)) scale(1.08);
}

.like-pin.popping {
  animation: pin-pop 0.34s cubic-bezier(0.34, 1.56, 0.64, 1);
}

@keyframes pin-pop {
  0%   { transform: scale(0)    rotate(var(--pin-tilt, 0deg)); }
  60%  { transform: scale(1.25) rotate(var(--pin-tilt, 0deg)); }
  100% { transform: scale(1)    rotate(var(--pin-tilt, 0deg)); }
}

.like-overflow {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--ink-muted);
  background: rgba(255, 255, 255, 0.88);
  padding: 1px 6px 2px;
  border-radius: 999px;
  align-self: center;
  -webkit-backdrop-filter: blur(4px);
          backdrop-filter: blur(4px);
  /* box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08); */
}

/* ── Lightbox ─────────────────────────────────────── */
#lightbox {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 500;
  background: rgba(255, 255, 255, 0.92);
  align-items: center;
  justify-content: center;
  flex-direction: column;
  gap: 20px;
  backdrop-filter: blur(24px);
  -webkit-backdrop-filter: blur(24px);
}

#lightbox.open {
  display: flex;
}

#lb-img {
  max-width: min(80vw, 720px);
  max-height: 72vh;
  border-radius: 6px;
  object-fit: contain;
  display: block;
  box-shadow: 0 12px 48px rgba(0, 0, 0, 0.12);
}

#lb-meta {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-muted);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

#lb-close {
  position: fixed;
  top: 24px;
  right: 32px;
  color: var(--ink-muted);
  font-size: 20px;
  cursor: pointer;
  transition: color 0.12s;
  line-height: 1;
  font-weight: 300;
}

#lb-close:hover {
  color: var(--ink);
}

#lb-delete {
  background: transparent;
  border: 1px solid var(--ink-faint);
  color: var(--ink-muted);
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 8px 16px;
  border-radius: 999px;
  cursor: pointer;
  transition: color 0.12s, border-color 0.12s, background 0.12s;
}

#lb-delete:hover {
  color: #c0233e;
  border-color: #c0233e;
}

#lb-delete[hidden] {
  display: none;
}

/* ── Lightbox caption ─────────────────────────────── */
#lb-caption-wrap {
  width: min(80vw, 720px);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}

#lb-caption-display {
  font-size: 15px;
  line-height: 1.45;
  color: var(--ink);
  text-align: center;
  max-width: 100%;
  word-wrap: break-word;
  padding: 4px 12px;
  border-radius: 8px;
  display: inline-flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
  justify-content: center;
}

#lb-caption-display.owner {
  cursor: pointer;
  transition: background 0.12s;
}

#lb-caption-display.owner:hover {
  background: rgba(13, 13, 13, 0.04);
}

.lb-caption-edit-hint,
.lb-caption-add {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-muted);
}

#lb-caption-display.empty .lb-caption-add {
  font-size: 11px;
}

#lb-caption-form {
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: min(80vw, 480px);
}

#lb-caption-form[hidden] {
  display: none;
}

#lb-caption-input {
  width: 100%;
  resize: vertical;
  min-height: 56px;
  padding: 10px 12px;
  font-family: var(--font);
  font-size: 14px;
  line-height: 1.45;
  color: var(--ink);
  background: var(--bg);
  border: 1px solid var(--ink-faint);
  border-radius: 8px;
  outline: none;
  transition: border-color 0.12s;
}

#lb-caption-input:focus {
  border-color: var(--ink);
}

#lb-caption-row {
  display: flex;
  align-items: center;
  gap: 8px;
}

#lb-caption-count {
  margin-right: auto;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.05em;
  color: var(--ink-muted);
}

#lb-caption-cancel,
#lb-caption-save {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 8px 16px;
  border-radius: 999px;
  cursor: pointer;
  border: 1px solid var(--ink-faint);
  background: transparent;
  color: var(--ink-muted);
  transition: color 0.12s, border-color 0.12s, background 0.12s;
}

#lb-caption-cancel:hover {
  color: var(--ink);
  border-color: var(--ink);
}

#lb-caption-save {
  color: #fff;
  background: var(--ink);
  border-color: var(--ink);
}

#lb-caption-save:hover {
  background: #000;
  border-color: #000;
}

#lb-caption-save:disabled,
#lb-caption-cancel:disabled {
  opacity: 0.5;
  cursor: default;
}

/* ── Confirm modal ────────────────────────────────── */
#confirm-modal {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 600;
  background: rgba(13, 13, 13, 0.32);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  align-items: center;
  justify-content: center;
}

#confirm-modal.open {
  display: flex;
}

#confirm-card {
  background: var(--bg);
  border-radius: 10px;
  padding: 28px 28px 20px;
  width: min(360px, calc(100vw - 40px));
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.18);
  display: flex;
  flex-direction: column;
  gap: 8px;
}

#confirm-title {
  font-size: 16px;
  font-weight: 500;
  color: var(--ink);
  letter-spacing: -0.01em;
}

#confirm-body {
  font-size: 13px;
  color: var(--ink-muted);
  line-height: 1.5;
}

#confirm-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 16px;
}

#confirm-actions button {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 8px 16px;
  border-radius: 999px;
  cursor: pointer;
  border: 1px solid var(--ink-faint);
  background: transparent;
  color: var(--ink-muted);
  transition: color 0.12s, border-color 0.12s, background 0.12s;
}

#confirm-cancel:hover {
  color: var(--ink);
  border-color: var(--ink);
}

#confirm-ok {
  color: #fff;
  background: #c0233e;
  border-color: #c0233e;
}

#confirm-ok:hover {
  background: #a31c33;
  border-color: #a31c33;
}

#confirm-ok:disabled,
#confirm-cancel:disabled {
  opacity: 0.5;
  cursor: default;
}

/* Bottom pad */
#bottom-pad {
  height: 80px;
}

/* ── Invite button + modal ───────────────────────── */
#invite-btn {
  margin-left: auto;
  background: transparent;
  border: 1px solid var(--ink-faint);
  border-radius: 999px;
  padding: 4px 12px;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  cursor: pointer;
  color: var(--ink-muted);
  transition: color 0.12s, border-color 0.12s;
}
#invite-btn:hover {
  color: var(--ink);
  border-color: var(--ink-muted);
}

#invite-modal {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 600;
  background: rgba(13, 13, 13, 0.32);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  align-items: center;
  justify-content: center;
}
#invite-modal.open { display: flex; }

#invite-card {
  background: var(--bg);
  border-radius: 10px;
  padding: 28px 28px 20px;
  width: min(440px, calc(100vw - 40px));
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.18);
  display: flex;
  flex-direction: column;
  gap: 8px;
}
#invite-title {
  font-size: 16px;
  font-weight: 500;
  color: var(--ink);
  letter-spacing: -0.01em;
}
#invite-body {
  font-size: 13px;
  color: var(--ink-muted);
  line-height: 1.5;
}
#invite-link-row {
  display: flex;
  gap: 8px;
  margin-top: 14px;
}
#invite-link {
  flex: 1 1 0;
  min-width: 0;
  padding: 10px 12px;
  font-family: var(--font-mono);
  font-size: 12px;
  border: 1px solid var(--ink-faint);
  border-radius: 8px;
  background: var(--bg);
  color: var(--ink);
}
#invite-link:focus { outline: none; border-color: var(--ink); }
#invite-copy {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 0 16px;
  border-radius: 8px;
  border: 1px solid var(--ink);
  background: var(--ink);
  color: var(--bg);
  cursor: pointer;
}
#invite-copy.copied { background: #2a8a4a; border-color: #2a8a4a; }

#invite-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 16px;
}
#invite-actions button {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 8px 16px;
  border-radius: 999px;
  cursor: pointer;
  border: 1px solid var(--ink-faint);
  background: transparent;
  color: var(--ink-muted);
  transition: color 0.12s, border-color 0.12s;
}
#invite-actions button:hover {
  color: var(--ink);
  border-color: var(--ink);
}

/* ── View switching ───────────────────────────────── */
/* The two views share #scroll-wrap so pull-to-refresh wires once.
   The body class decides which renders. */
body.view-wall #carousel { display: none; }
body.view-carousel #canvas { display: none; }
body.view-carousel #scroll-wrap { overflow-x: hidden; }

/* ── Carousel view ─────────────────────────────────
   Vertical stack of hour blocks. Each block: a sticky-style hour
   tag at top-left and a horizontally-scrollable row of photo cards.
   Mobile-first sizes; cards grow modestly on wider viewports. */
#carousel {
  padding: 8px 0 96px 0;
  width: 100%;
  max-width: 720px;
  margin: 0 auto;
}

.hour-block {
  position: relative;
  margin: 0 0 16px 0;
  padding: 0;
}

.hour-block:first-child {
  margin-top: 4px;
}

/* The tag floats absolutely above the strip's top-left so it visually
   "hangs off" the first card — like a tab clipped to the row. */
.hour-tag {
  position: absolute;
  top: 0;
  left: 22px;
  z-index: 3;
  background: #fff;
  color: var(--ink);
  font-size: 20px;
  font-weight: 500;
  letter-spacing: -0.02em;
  padding: 7px 14px 8px 14px;
  border-radius: 10px;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06),
              0 6px 16px rgba(0, 0, 0, 0.05);
  line-height: 1;
  pointer-events: none;
}

.hour-strip {
  display: flex;
  gap: 12px;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  padding: 26px 14px 14px 14px;
}

.hour-strip::-webkit-scrollbar { display: none; }

.hour-strip.empty {
  padding: 14px;
  color: var(--ink-muted);
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.05em;
}

.h-card {
  flex: 0 0 auto;
  width: clamp(180px, 62vw, 240px);
  aspect-ratio: 3 / 4;
  border-radius: 14px;
  background: #ebe9e3;
  position: relative;
  scroll-snap-align: start;
  cursor: pointer;
  /* Match .photo-card: tight 2px resting shadow, full lift on hover. */
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
  transform: rotate(var(--rot, 0deg));
  transition: transform 0.22s ease, box-shadow 0.22s ease;
}

.h-card:hover {
  transform: rotate(0deg) scale(1.02);
  box-shadow: 0 12px 28px rgba(0, 0, 0, 0.14);
  z-index: 5;
}

.h-card img {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: 14px;
  -o-object-fit: cover;
     object-fit: cover;
}

.h-name {
  position: absolute;
  top: 10px;
  left: 10px;
  background: rgba(255, 255, 255, 0.78);
  -webkit-backdrop-filter: blur(6px);
          backdrop-filter: blur(6px);
  color: var(--ink);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.005em;
  padding: 4px 10px 5px;
  border-radius: 8px;
  z-index: 2;
  max-width: calc(100% - 20px);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ── Pull-to-refresh indicator ────────────────────── */
/* Lives outside the scroll area so it can sit above the topbar. JS sets
   `transform` while the user pulls; clearing it lets the CSS default
   translate take over and snap the indicator back out of view. */
#ptr-indicator {
  position: fixed;
  top: 12px;
  left: 50%;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--bg);
  border: 1px solid var(--ink-faint);
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.06);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 400;
  pointer-events: none;
  color: var(--ink-muted);
  transform: translate(-50%, -64px);
  transition: transform 0.22s ease, color 0.15s ease;
  will-change: transform;
}

#ptr-indicator svg {
  width: 16px;
  height: 16px;
  display: block;
}

#ptr-indicator.ready {
  color: var(--ink);
}

#ptr-indicator.refreshing svg {
  animation: ptr-spin 0.8s linear infinite;
}

@keyframes ptr-spin {
  to { transform: rotate(360deg); }
}