/* Photos — gallery view over the user's uploaded images across the
   Toothpaste suite. Tile layout uses a CSS grid with auto-fill so
   the column count reflows naturally with the viewport. The
   lightbox is a single full-viewport overlay; navigating with the
   prev/next buttons (or arrow keys) cycles through whatever the
   current filter is showing, not the underlying full set. */

:root {
    --photos-tile-size: 180px;
    --photos-tile-gap: 6px;
}

.appLayout {
    padding-top: var(--header-height);
    min-height: 100vh;
}

.photosPage {
    width: 100%;
    max-width: 1280px;
    margin: 0 auto;
    padding: 24px 24px 80px;
}

.photosPageHeader {
    margin-bottom: 16px;
}
.photosPageEyebrow {
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--text-tertiary);
    margin-bottom: 6px;
}
.photosPageTitle {
    font-size: 28px;
    font-weight: 600;
    letter-spacing: -0.02em;
    color: var(--text);
    margin: 0;
}
.photosPageSub {
    margin: 6px 0 0;
    font-size: 14px;
    color: var(--text-secondary);
}

/* Source-filter chips + the running total. Sit in one row so the
   summary ("128 photos · 412 MB") rides alongside the filters when
   there's room and tucks below them when the viewport is tight. */
.photosToolbar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 10px;
    margin: 18px 0 14px;
}
.photosFilters {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}
.photosFilterChip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 5px 12px;
    border-radius: 999px;
    border: 1px solid var(--border);
    background: var(--bg);
    color: var(--text-secondary);
    font-size: 13px;
    font-weight: 500;
    cursor: pointer;
    transition: background var(--transition), border-color var(--transition), color var(--transition);
}
.photosFilterChip:hover {
    background: var(--bg-hover);
    color: var(--text);
}
.photosFilterChip.active {
    background: var(--accent-soft);
    border-color: var(--accent);
    color: var(--text);
}
.photosFilterCount {
    font-size: 11.5px;
    color: var(--text-tertiary);
    font-variant-numeric: tabular-nums;
}
.photosFilterChip.active .photosFilterCount {
    color: var(--text-secondary);
}

/* Media-kind selector (Photos / Photos and Videos / Videos). A
   segmented control rather than separate chips so the three options
   read as mutually exclusive — picking one always replaces the
   previous selection. Hidden via JS when the gallery has only one
   media kind to show. */
.photosKindFilter {
    display: inline-flex;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--bg);
    overflow: hidden;
}
.photosKindFilter[hidden] { display: none; }
.photosKindChip {
    appearance: none;
    border: 0;
    background: transparent;
    padding: 5px 12px;
    font: inherit;
    font-size: 12.5px;
    font-weight: 500;
    color: var(--text-secondary);
    cursor: pointer;
    transition: background var(--transition), color var(--transition);
}
.photosKindChip + .photosKindChip { border-left: 1px solid var(--border); }
.photosKindChip:hover { background: var(--bg-hover); color: var(--text); }
.photosKindChip.active {
    background: var(--accent-soft);
    color: var(--text);
}

.photosStats {
    margin-left: auto;
    font-size: 12.5px;
    color: var(--text-tertiary);
    font-variant-numeric: tabular-nums;
}

/* Toolbar action buttons (Upload + Hidden-folders). The primary
   variant carries the suite's accent so the upload CTA reads as
   the foreground action; the ghost variant is a quiet secondary. */
.photosToolbarBtn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 7px 14px;
    border-radius: 999px;
    border: 1px solid var(--border);
    background: var(--bg);
    color: var(--text-secondary);
    font: inherit;
    font-size: 13px;
    font-weight: 500;
    cursor: pointer;
    transition: background var(--transition), color var(--transition), border-color var(--transition), transform var(--transition);
}
.photosToolbarBtn:hover { background: var(--bg-hover); color: var(--text); border-color: var(--border-strong); }
.photosToolbarBtn:active { transform: translateY(0.5px); }
.photosToolbarBtn--primary {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--bg-sidebar);
}
.photosToolbarBtn--primary:hover {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--bg-sidebar);
    filter: brightness(1.06);
}

/* Folders section — list of distinct directories that contain
   photos. Each row carries a kebab → "Hide folder" so users can
   scope down without leaving the gallery. Sits above the photo
   grid so the act of hiding is symmetric with the act of seeing.
   Hidden when there's only one folder (no folder choice to make). */
/* Upload progress strip — quiet inline list above the gallery
   that shows what's currently in flight. Self-clears once
   everything resolves. */
.photosUploadStrip {
    margin-bottom: 14px;
    padding: 8px 12px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--bg);
    display: flex;
    flex-direction: column;
    gap: 4px;
}
/* Explicit hidden override — `display: flex` above ties with the UA's
   [hidden] { display: none } rule (both class/attribute specificity)
   and the author stylesheet wins the tie, leaving the strip visible
   even when JS has set the `hidden` attribute. */
.photosUploadStrip[hidden] { display: none; }
.photosUploadRow {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    font-size: 12.5px;
}
.photosUploadName {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--text);
}
.photosUploadStatus { color: var(--text-tertiary); flex-shrink: 0; }
.photosUploadRow.is-done .photosUploadStatus,
.photosUploadRow--done .photosUploadStatus { color: #1f6b3d; }
.photosUploadRow.is-error .photosUploadStatus,
.photosUploadRow--error .photosUploadStatus { color: #c33; }
.photosUploadRow--pending .photosUploadStatus { color: var(--text-secondary); }
.photosUploadRow--error .photosUploadName { color: var(--text-secondary); }
/* Retry / Dismiss buttons that appear on error rows. Match the
   existing chip-style buttons elsewhere in Photos so the strip
   doesn't introduce a new control style. */
.photosUploadAction {
    flex-shrink: 0;
    padding: 4px 10px;
    border-radius: 999px;
    border: 1px solid var(--border);
    background: var(--bg);
    color: var(--text-secondary);
    font-size: 12px;
    cursor: pointer;
    transition: background var(--transition), color var(--transition), border-color var(--transition);
}
.photosUploadAction:hover {
    background: var(--bg-hover);
    color: var(--text);
    border-color: var(--border-strong);
}

/* Loading + empty + error placeholders. */
.photosLoading,
.photosEmpty,
.photosError {
    padding: 60px 20px;
    text-align: center;
    color: var(--text-secondary);
}
.photosEmpty .photosEmptyTitle {
    font-size: 18px;
    font-weight: 600;
    color: var(--text);
    margin-bottom: 6px;
}
.photosEmpty .photosEmptySub {
    font-size: 14px;
    line-height: 1.5;
    max-width: 480px;
    margin: 0 auto;
}
.photosError {
    color: #c33;
}

/* Stale-cache banner — shown when the API call fails but we still
   have a cached photo list to render. Keeps the gallery visible so
   a phone on a flaky connection still feels like a gallery app, not
   a broken page. */
.photosOfflineBanner {
    margin: 0 auto 12px;
    max-width: 720px;
    padding: 10px 14px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--bg-elev, var(--bg));
    color: var(--text-secondary);
    font-size: 13px;
    text-align: center;
}

/* Logged-out CTA card. */
.photosOnboard {
    margin: 60px auto 0;
    max-width: 520px;
    padding: 32px;
    text-align: center;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--bg);
}
.photosOnboardTitle {
    font-size: 20px;
    font-weight: 600;
    color: var(--text);
    margin-bottom: 8px;
}
.photosOnboardSub {
    font-size: 14px;
    line-height: 1.55;
    color: var(--text-secondary);
    margin-bottom: 18px;
}
.photosSignInBtn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 10px 20px;
    border-radius: var(--radius-sm);
    background: var(--accent);
    color: var(--bg-sidebar);
    font-size: 14px;
    font-weight: 600;
    text-decoration: none;
    transition: transform 120ms ease, box-shadow 160ms ease;
}
.photosSignInBtn:hover {
    transform: translateY(-0.5px);
    box-shadow: 0 3px 8px rgba(0, 0, 0, 0.16);
}

/* ── Gallery: month-grouped timeline ────────────────────────────
   Photos are grouped by capture month (with upload-month as the
   fallback) and rendered as a stack of <section>s. The month
   title sticks to the top of the viewport as the user scrolls
   past it — same affordance Google Photos uses to anchor the
   user in time. */
.photosGrid {
    display: flex;
    flex-direction: column;
    gap: 28px;
}
.photosMonth { display: block; }
.photosMonthTitle {
    position: sticky;
    top: var(--header-height, 56px);
    z-index: 5;
    margin: 0 0 8px;
    padding: 8px 2px 10px;
    font-size: 17px;
    font-weight: 600;
    letter-spacing: -0.01em;
    color: var(--text);
    background: var(--bg);
    /* Soft hairline so the sticky header reads cleanly when
       there are tiles peeking under it. */
    box-shadow: 0 1px 0 var(--border);
}
.photosMonthGrid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(var(--photos-tile-size), 1fr));
    gap: var(--photos-tile-gap);
}
.photosTile {
    position: relative;
    aspect-ratio: 1 / 1;
    overflow: hidden;
    background: var(--bg-sidebar);
    border-radius: var(--radius-sm);
    cursor: pointer;
    transition: transform 120ms ease, box-shadow 160ms ease;
}
.photosTile:hover {
    transform: translateY(-1px);
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.10);
}
.photosTile img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    background: var(--bg-sidebar);
    transition: opacity 200ms ease;
}
.photosTile img[data-loaded="false"] {
    opacity: 0;
}
.photosTile img[data-loaded="true"] {
    opacity: 1;
}
/* Video tiles share the same square footprint; the <video> element
   shows the first frame as the poster (preload="metadata" + #t=0.1)
   so we don't need a thumbnail service. The play badge sits in the
   centre to signal "click for playback". */
.photosTile video {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    background: var(--bg-sidebar);
    transition: opacity 200ms ease;
    pointer-events: none; /* let the wrapping button receive the click */
}
.photosTile video[data-loaded="false"] { opacity: 0; }
.photosTile video[data-loaded="true"]  { opacity: 1; }
.photosTilePlayBadge {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 44px;
    height: 44px;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.55);
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 16px;
    backdrop-filter: blur(4px);
    pointer-events: none;
    transition: transform 120ms ease, background 120ms ease;
}
.photosTile--video:hover .photosTilePlayBadge {
    background: rgba(0, 0, 0, 0.7);
    transform: translate(-50%, -50%) scale(1.06);
}
.photosTile .photosTileBadge {
    position: absolute;
    top: 6px;
    left: 6px;
    padding: 2px 8px;
    background: rgba(0, 0, 0, 0.55);
    color: #fff;
    font-size: 10.5px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    border-radius: 999px;
    backdrop-filter: blur(4px);
    opacity: 0;
    transition: opacity 120ms ease;
}
.photosTile:hover .photosTileBadge {
    opacity: 1;
}
/* Always-on filename / date strip across the bottom of each tile.
   Slides in from the bottom edge — visible immediately on small
   tiles where the badge alone doesn't carry enough context. */
.photosTile .photosTileInfo {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    padding: 6px 8px 5px;
    background: linear-gradient(to top, rgba(0,0,0,0.65) 0%, rgba(0,0,0,0.0) 100%);
    color: rgba(255,255,255,0.95);
    font-size: 11px;
    line-height: 1.3;
    pointer-events: none;
    display: flex;
    flex-direction: column;
    gap: 1px;
    opacity: 0;
    transition: opacity 140ms ease;
}
.photosTile:hover .photosTileInfo,
.photosTile:focus-visible .photosTileInfo {
    opacity: 1;
}
.photosTileInfoName {
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.photosTileInfoMeta {
    font-size: 10.5px;
    color: rgba(255,255,255,0.7);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.photosTile.photosTile--broken {
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--text-tertiary);
    font-size: 11px;
    text-align: center;
    padding: 8px;
}
.photosTile.photosTile--broken img { display: none; }

/* "Too large to preview" placeholder tile. Same dimensions as a
   regular tile (aspect-ratio is inherited) but shows a small icon
   + filename + size instead of fetching the original bytes — so
   browsing the grid stays fast even when the user has a couple of
   60 MB panoramas mixed in. */
.photosTile--toolarge {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 4px;
    padding: 12px;
    text-align: center;
    background: var(--bg-sidebar);
    color: var(--text-secondary);
}
.photosTilePlaceholderIcon {
    font-size: 26px;
    color: var(--text-tertiary);
    margin-bottom: 4px;
}
.photosTilePlaceholderName {
    font-size: 11.5px;
    font-weight: 600;
    color: var(--text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 100%;
}
.photosTilePlaceholderSize {
    font-size: 10.5px;
    color: var(--text-tertiary);
    font-variant-numeric: tabular-nums;
}

/* ── Photo viewer ──────────────────────────────────────────────── */
/* Modeled on iOS Photos / Apple Photos: solid black backdrop, two
   translucent blurred bars (top + bottom) over the image, circular
   chevron nav on the sides. Tapping the image hides the chrome
   (data-chrome=false on the dialog) for an immersive view; tapping
   the surrounding stage closes the viewer. */
.photosLightbox {
    position: fixed;
    inset: 0;
    z-index: 300;
    background: #000;
    display: none;
    overflow: hidden;
    -webkit-tap-highlight-color: transparent;
    /* iOS PWA — pad for the dynamic island / home indicator. */
    padding-top:    env(safe-area-inset-top);
    padding-bottom: env(safe-area-inset-bottom);
    padding-left:   env(safe-area-inset-left);
    padding-right:  env(safe-area-inset-right);
    animation: photosLightboxFadeIn 200ms ease;
}
.photosLightbox[data-open="true"] {
    display: block;
}
@keyframes photosLightboxFadeIn {
    from { opacity: 0; }
    to   { opacity: 1; }
}

/* Stage — the canvas the photo sits on. Fills the whole viewport
   so a tap anywhere outside the image hits the close-on-stage
   handler. */
.photosLightboxStage {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    cursor: zoom-out;
}
.photosLightboxStage img {
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
    user-select: none;
    -webkit-user-drag: none;
    transition: opacity 180ms ease;
    cursor: pointer;
}
.photosLightboxStage img[data-loaded="false"] { opacity: 0; }
.photosLightboxStage img[data-loaded="true"]  { opacity: 1; }
.photosLightboxStage video {
    max-width: 100%;
    max-height: 100%;
    background: #000;
    outline: none;
    user-select: none;
}
.photosLightboxSpinner {
    position: absolute;
    width: 32px;
    height: 32px;
    border: 2.5px solid rgba(255,255,255,0.18);
    border-top-color: rgba(255,255,255,0.85);
    border-radius: 50%;
    animation: photosLightboxSpin 800ms linear infinite;
    pointer-events: none;
}
@keyframes photosLightboxSpin {
    to { transform: rotate(360deg); }
}

/* Floating control rows. The bars themselves are transparent
   layout containers — no blur, no full-width background — so the
   image renders edge-to-edge under them and isn't visually
   sliced by a frosted strip. Individual controls (close, action
   buttons, metadata pill) carry their own dark chip backgrounds
   so they stay readable against any image. */
.photosLightboxBar {
    position: absolute;
    left: 0;
    right: 0;
    z-index: 2;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 12px 14px;
    color: #fff;
    transition: opacity 200ms ease, transform 200ms ease;
    pointer-events: none;
}
.photosLightboxBar > * { pointer-events: auto; }
.photosLightboxBar--top    { top: 0; }
.photosLightboxBar--bottom { bottom: 0; justify-content: center; padding: 14px; }
.photosLightbox[data-chrome="false"] .photosLightboxBar--top {
    opacity: 0;
    transform: translateY(-100%);
    pointer-events: none;
}
.photosLightbox[data-chrome="false"] .photosLightboxBar--bottom {
    opacity: 0;
    transform: translateY(100%);
    pointer-events: none;
}

.photosLightboxTitleBlock {
    flex: 1;
    min-width: 0;
    text-align: center;
}
.photosLightboxTitle {
    font-size: 14.5px;
    font-weight: 600;
    letter-spacing: -0.005em;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: #fff;
    /* No bar background under the title anymore — a soft drop
       shadow keeps it legible against bright images. */
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.55), 0 0 12px rgba(0, 0, 0, 0.35);
}
.photosLightboxCounter {
    font-size: 11.5px;
    color: rgba(255,255,255,0.85);
    font-variant-numeric: tabular-nums;
    margin-top: 1px;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}
.photosLightboxActions {
    display: flex;
    align-items: center;
    gap: 6px;
}

/* Icon button — same size + interaction model as the side nav so
   the entire viewer feels like one cohesive control surface. */
.photosLightboxIconBtn {
    width: 36px;
    height: 36px;
    border: 0;
    border-radius: 50%;
    background: rgba(255,255,255,0.10);
    color: #fff;
    font-size: 15px;
    line-height: 1;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    transition: background 120ms ease, transform 120ms ease, color 120ms ease;
}
.photosLightboxIconBtn:hover    { background: rgba(255,255,255,0.18); }
.photosLightboxIconBtn:active   { transform: scale(0.95); }
.photosLightboxIconBtn:disabled { opacity: 0.5; cursor: default; }
/* Destructive variant — same circular pill, but the icon goes
   coral on hover so it reads as different from the safe actions
   (close, open, download) without screaming for attention while
   idle. */
.photosLightboxIconBtn--danger:hover {
    background: rgba(220, 70, 70, 0.85);
    color: #fff;
}

/* In-viewer delete confirm. Overlays the image without closing the
   viewer so the user keeps their place in the gallery. */
.photosLightboxConfirm {
    position: absolute;
    inset: 0;
    z-index: 4;
    display: none;
    align-items: center;
    justify-content: center;
    padding: 20px;
    background: rgba(0, 0, 0, 0.55);
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
}
.photosLightboxConfirm[data-open="true"] {
    display: flex;
    animation: photosLightboxFadeIn 140ms ease;
}
.photosLightboxConfirmCard {
    width: 100%;
    max-width: 360px;
    padding: 24px 22px 18px;
    background: var(--bg);
    color: var(--text);
    border-radius: var(--radius);
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.45);
    text-align: center;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
}
.photosLightboxConfirmIcon {
    width: 48px;
    height: 48px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(220, 70, 70, 0.12);
    color: #c33;
    font-size: 18px;
}
.photosLightboxConfirmTitle {
    font-size: 16px;
    font-weight: 600;
    letter-spacing: -0.01em;
}
.photosLightboxConfirmSub {
    font-size: 13px;
    color: var(--text-secondary);
    line-height: 1.45;
    word-break: break-word;
}
.photosLightboxConfirmActions {
    display: flex;
    gap: 8px;
    width: 100%;
    margin-top: 6px;
}
.photosLightboxConfirmBtn {
    flex: 1;
    padding: 10px 14px;
    border-radius: var(--radius-sm);
    border: 1px solid var(--border);
    background: var(--bg);
    color: var(--text);
    font: inherit;
    font-size: 13.5px;
    font-weight: 600;
    cursor: pointer;
    transition: background 120ms ease, border-color 120ms ease, color 120ms ease, transform 120ms ease;
}
.photosLightboxConfirmBtn:hover { background: var(--bg-hover); }
.photosLightboxConfirmBtn:active { transform: translateY(0.5px); }
.photosLightboxConfirmBtn:disabled { opacity: 0.6; cursor: default; transform: none; }
.photosLightboxConfirmBtn--danger {
    background: #c33;
    border-color: #c33;
    color: #fff;
}
.photosLightboxConfirmBtn--danger:hover {
    background: #b22b2b;
    border-color: #b22b2b;
    color: #fff;
}

/* Side navigation. Vertically centered floating chevrons; visibility
   driven by the JS (hidden at the ends rather than just dimmed). */
.photosLightboxNav {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    z-index: 3;
    width: 44px;
    height: 44px;
    border-radius: 50%;
    border: 0;
    background: rgba(20, 20, 22, 0.55);
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    color: #fff;
    font-size: 16px;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 120ms ease, transform 120ms ease, opacity 120ms ease;
}
.photosLightboxNav:hover  { background: rgba(40, 40, 42, 0.75); }
.photosLightboxNav:active { transform: translateY(-50%) scale(0.95); }
.photosLightboxNav--prev  { left: 16px; }
.photosLightboxNav--next  { right: 16px; }
.photosLightbox[data-chrome="false"] .photosLightboxNav {
    opacity: 0;
    pointer-events: none;
}

/* "Too large to preview" panel — replaces the <img> in the stage
   when a file blows past MAX_PREVIEW_BYTES. Sits centered on the
   black backdrop. Download / open buttons in the top toolbar
   stay live; this panel just explains why the bytes haven't
   downloaded automatically. */
.photosLightboxTooLarge {
    color: rgba(255,255,255,0.85);
    text-align: center;
    padding: 24px;
    max-width: 440px;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
}
.photosLightboxTooLargeIcon {
    width: 64px;
    height: 64px;
    border-radius: 50%;
    background: rgba(255,255,255,0.08);
    color: rgba(255,255,255,0.7);
    font-size: 26px;
    display: flex;
    align-items: center;
    justify-content: center;
    margin-bottom: 4px;
}
.photosLightboxTooLargeTitle {
    font-size: 18px;
    font-weight: 600;
    color: #fff;
}
.photosLightboxTooLargeSub {
    font-size: 13.5px;
    color: rgba(255,255,255,0.6);
    word-break: break-all;
}
.photosLightboxTooLargeHint {
    font-size: 12.5px;
    color: rgba(255,255,255,0.55);
    line-height: 1.45;
    max-width: 320px;
}

/* Bottom metadata pill — single rounded chip with the source +
   date + size triple, like the iOS Photos info caption. */
.photosLightboxMetaChip {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 6px 14px;
    background: rgba(255,255,255,0.10);
    color: rgba(255,255,255,0.92);
    border-radius: 999px;
    font-size: 12.5px;
    font-variant-numeric: tabular-nums;
    max-width: 90%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ── Footer ────────────────────────────────────────────────────── */
/* Mirrors saved/public_html/common/css/app.css's .appFooter so
   Photos's footer reads the same as the rest of the suite — pinned
   to the viewport bottom with a hairline divider. The page's
   bottom padding (80px) already clears it. */
.appFooter {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 90;
    background: var(--bg);
    border-top: 1px solid var(--border);
    padding: 16px 24px;
    text-align: center;
    color: var(--text-tertiary);
    font-size: 12px;
}
.appFooterMeta { margin: 0; }

/* ── Mobile ────────────────────────────────────────────────────── */
@media (max-width: 768px) {
    :root {
        --photos-tile-size: 120px;
        --photos-tile-gap: 4px;
    }
    .photosPage { padding: 16px 12px 80px; }
    .photosPageTitle { font-size: 22px; }
    .photosStats { margin-left: 0; flex-basis: 100%; }
    .photosLightboxBar { padding: 10px 10px; }
    .photosLightboxTitle { font-size: 13.5px; }
    .photosLightboxNav {
        width: 40px;
        height: 40px;
        font-size: 14px;
    }
    .photosLightboxNav--prev { left: 8px; }
    .photosLightboxNav--next { right: 8px; }
}

@media (max-width: 480px) {
    :root { --photos-tile-size: 100px; }
}
