/* Nuotraukų katalogas — guest album + owner admin.

   The look and the motion are lifted from the promo video that advertises
   this app ("google share video templ" / promo-scenes.jsx), so the page a
   guest lands on is recognisably the thing they were just shown: paper
   ground, ink type, a single gold accent, line-art in the corners, and the
   video's three motion primitives — enter, pop, draw.

   One deliberate departure from the video: its display face is Cormorant
   Garamond, which renders s-caron and z-caron with the caron detached and
   floating above the letter. This app's interface is entirely Lithuanian
   ("Svečiai", "Pažymėti", "Įkelkite"), so it uses Playfair Display instead —
   the same face deividaskvederys.lt switched to for exactly this reason. */

:root {
  /* Palette straight from promo-scenes.jsx */
  --paper:  #f7f4ef;   /* PAPER  — page ground */
  --card:   #ffffff;   /* CARD   */
  --ink:    #26211a;   /* INK    — type, primary buttons */
  --line:   #d9d1c3;   /* LINE   — hairlines */
  --soft:   #8d8272;   /* SOFT   — muted type */
  --acc:    #a4763b;   /* ACC    — the single accent */
  --acc-dark: #8a5f2b;
  --chrome: #fbf9f5;   /* the video's browser-chrome bar */
  --pill:   #f1ede5;   /* its address pill */
  --danger: #a8352a;
  --ok:     #4f7a45;

  /* Split out of the rules below so a dark theme has something to override.
     Primary buttons used to be `background: var(--ink); color: #fff`, which
     inverts into a near-white button with white text the moment --ink turns
     light. */
  --btn-bg:    var(--ink);
  --btn-fg:    #ffffff;
  --btn-hover: #352e25;
  --tint:      #fbf7f1;   /* selected option / drag-over wash */
  --err-bg:    #fbf1ef;
  --err-line:  #e8c9c4;
  --err-fg:    #8d2a21;
  --art-op:    .55;       /* line-art watermark strength */

  --radius: 14px;
  --shadow-sm: 0 2px 6px rgba(38, 33, 26, .08);
  --shadow:    0 10px 26px rgba(38, 33, 26, .10);
  --shadow-lg: 0 30px 70px rgba(38, 33, 26, .18);

  --serif: 'Playfair Display', Georgia, serif;
  --sans:  Helvetica, Arial, sans-serif;

  /* The video's easings, as bezier equivalents */
  --out:  cubic-bezier(.215, .61, .355, 1);   /* easeOutCubic  — enter */
  --back: cubic-bezier(.34, 1.56, .64, 1);    /* easeOutBack   — pop   */
  --io:   cubic-bezier(.645, .045, .355, 1);  /* easeInOutCubic       */
}

/* ---------- Dark theme ----------
   Same palette rotated, not a different design: warm near-black paper, the
   gold lifted just enough to keep its contrast, and the primary button
   flipped to light-on-dark rather than left as ink that no longer reads.

   Dark is opt-in, not inherited. Following prefers-color-scheme meant a
   guest whose phone happens to be in dark mode opened a wedding album in
   near-black without ever asking for it — and paper is the brand. The
   toggle is the only way in now, and the choice is remembered. */
:root[data-theme="dark"] {
  --paper:  #15120e;
  --card:   #1d1a15;
  --ink:    #efe9df;
  --line:   #342e25;
  --soft:   #a79b8a;
  --acc:    #c99a5c;
  --acc-dark: #b0854b;
  --chrome: #221e18;
  --pill:   #2b261e;
  --danger: #b34335;
  --btn-bg:    #efe9df;
  --btn-fg:    #17140f;
  --btn-hover: #ffffff;
  --tint:      #2a2318;
  --err-bg:    #2a1a17;
  --err-line:  #5a3129;
  --err-fg:    #f0b3a8;
  /* The corner line-art is drawn in the light hairline colour, baked into
     the SVG. At full strength on near-black it reads as scratches. */
  --art-op:    .16;
  --shadow-sm: 0 2px 6px rgba(0, 0, 0, .40);
  --shadow:    0 10px 26px rgba(0, 0, 0, .46);
  --shadow-lg: 0 30px 70px rgba(0, 0, 0, .60);
}

* { box-sizing: border-box; }
body {
  margin: 0;
  font-family: var(--sans);
  background: var(--paper);
  color: var(--ink);
  line-height: 1.5;
}
/* Native controls (date picker, select arrow, scrollbars, form autofill)
   follow this rather than staying stubbornly light inside a dark page. */
:root[data-theme="dark"] { color-scheme: dark; }
:root[data-theme="light"] { color-scheme: light; }

/* ---------- Line-art backdrop ----------
   The video's Backdrop(): concentric circles in the top-right, a film strip
   in the bottom-left, drawn in hairline. Fixed and non-interactive, so it
   sits still behind the page the way it sits still behind the scenes.
   Hidden on small screens, where there is no margin to spare for it. */
body::before, body::after {
  content: ""; position: fixed; pointer-events: none; z-index: 0;
  background-repeat: no-repeat;
  /* In the video these sit at the edge of a fixed 1280×720 frame. On a page
     that scrolls they are always at the viewport edge, where full-strength
     hairline reads as a stray UI element rather than as watermark. */
  opacity: var(--art-op);
}
body::before {
  top: -40px; right: -40px; width: 300px; height: 300px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Cg fill='none' stroke='%23d9d1c3'%3E%3Ccircle cx='195' cy='120' r='130' stroke-width='1.5'/%3E%3Ccircle cx='195' cy='120' r='96'/%3E%3Ccircle cx='195' cy='120' r='60'/%3E%3C/g%3E%3Ccircle cx='40' cy='250' r='4' fill='%23d9d1c3'/%3E%3C/svg%3E");
}
body::after {
  bottom: 0; left: -50px; width: 380px; height: 90px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='380' height='90'%3E%3Cg fill='none' stroke='%23d9d1c3' stroke-width='1.5'%3E%3Crect x='0' y='10' width='378' height='70' rx='8'/%3E%3C/g%3E%3Cg fill='none' stroke='%23d9d1c3' stroke-width='1.5'%3E%3Crect x='22' y='28' width='38' height='34' rx='4'/%3E%3Crect x='80' y='28' width='38' height='34' rx='4'/%3E%3Crect x='138' y='28' width='38' height='34' rx='4'/%3E%3Crect x='196' y='28' width='38' height='34' rx='4'/%3E%3Crect x='254' y='28' width='38' height='34' rx='4'/%3E%3Crect x='312' y='28' width='38' height='34' rx='4'/%3E%3C/g%3E%3C/svg%3E");
}
@media (max-width: 900px) {
  body::before, body::after { display: none; }
}

.wrap { position: relative; z-index: 1; max-width: 900px; margin: 0 auto; padding: 20px 16px 60px; }

/* ---------- Type ---------- */
h1, h2, h3 { font-family: var(--serif); font-weight: 600; letter-spacing: .01em; }
header.appbar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 18px 0 14px; flex-wrap: wrap;
}
header.appbar h1 { font-size: 2.1rem; margin: 0; line-height: 1.15; }
header.appbar h1::after {
  /* The gold rule the video draws under every title. */
  content: ""; display: block; width: 90px; height: 1.5px; background: var(--acc);
  margin-top: 10px; transform-origin: left; animation: dc-rule .9s var(--out) both;
}
.badge {
  font-family: var(--serif); font-style: italic; font-size: 1.05rem; color: var(--acc);
}
.muted { color: var(--soft); font-size: .9rem; }
.center { text-align: center; }

/* ---------- Cards ---------- */
.card {
  position: relative;
  background: var(--card); border-radius: var(--radius);
  border: 1px solid var(--line); box-shadow: var(--shadow);
  padding: 22px; margin-bottom: 18px;
}
.card h2 { font-size: 1.5rem; margin: 0 0 14px; }
.card h3 { font-size: 1.25rem; }

/* ---------- Buttons ----------
   Primary is ink, not gold. In the video gold is reserved for rules, the
   italic accents and the click ring — the moment something is confirmed,
   never the furniture. */
.btn {
  position: relative; overflow: hidden;
  display: inline-flex; align-items: center; justify-content: center; gap: 10px;
  background: var(--btn-bg); color: var(--btn-fg); border: 1px solid var(--btn-bg);
  padding: 13px 26px; border-radius: 8px;
  font-family: var(--sans); font-size: .95rem; font-weight: 600; letter-spacing: .01em;
  cursor: pointer; text-decoration: none; box-shadow: var(--shadow-sm);
  transition: background .25s var(--out), border-color .25s var(--out),
              transform .25s var(--back), box-shadow .25s var(--out);
}
.btn:hover { background: var(--btn-hover); transform: translateY(-1px); box-shadow: var(--shadow); }
.btn:active { transform: translateY(0); }
.btn.secondary {
  background: var(--card); color: var(--ink); border-color: var(--line);
}
.btn.secondary:hover { background: var(--chrome); border-color: var(--soft); }
.btn.danger { background: var(--danger); border-color: var(--danger); color: #fff; }
.btn.danger:hover { background: #8d2a21; border-color: #8d2a21; }
.btn.small { padding: 8px 15px; font-size: .85rem; }
.btn:disabled { opacity: .5; cursor: not-allowed; transform: none; }
:focus-visible { outline: 2px solid var(--acc); outline-offset: 2px; }

/* The video's ClickRing: a gold ring that expands and fades from the point
   of contact. motion.js drops one of these in on pointerdown. */
.ring {
  position: absolute; width: 52px; height: 52px; margin: -26px 0 0 -26px;
  border: 2px solid var(--acc); border-radius: 50%; pointer-events: none;
  animation: dc-ring .5s var(--out) forwards;
}
@keyframes dc-ring {
  from { opacity: 1; transform: scale(.4); }
  to   { opacity: 0; transform: scale(1.3); }
}

/* ---------- Fields ---------- */
input[type=text], input[type=date], select, textarea {
  width: 100%; padding: 11px 13px; border: 1px solid var(--line);
  border-radius: 8px; font-family: var(--sans); font-size: 1rem;
  background: var(--card); color: var(--ink);
  transition: border-color .25s var(--out), box-shadow .25s var(--out);
}
input[type=text]:focus, input[type=date]:focus, select:focus, textarea:focus {
  outline: none; border-color: var(--acc); box-shadow: 0 0 0 3px rgba(164, 118, 59, .14);
}
label { font-size: .85rem; color: var(--soft); display: block; margin: 14px 0 5px; }
select { width: 100%; }

.error-box {
  background: var(--err-bg); border: 1px solid var(--err-line); color: var(--err-fg);
  padding: 12px 16px; border-radius: 10px; margin: 12px 0; display: none;
}

/* ---------- Owner links list (admin) ---------- */
.link-item {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 15px 0; border-bottom: 1px solid var(--line); flex-wrap: wrap;
}
.link-item:last-child { border-bottom: 0; }
.link-item .name { font-family: var(--serif); font-size: 1.2rem; font-weight: 600; }
.qr-holder {
  background: var(--card); padding: 14px; border-radius: 10px;
  border: 1px solid var(--line); display: inline-block;
}

/* ---------- Modals ---------- */
.modal-bg {
  position: fixed; inset: 0; background: rgba(38, 33, 26, .55);
  display: none; align-items: center; justify-content: center; padding: 16px; z-index: 50;
}
.modal-bg.open { display: flex; animation: dc-fade .3s var(--out) both; }
.modal {
  background: var(--card); border-radius: var(--radius); max-width: 420px; width: 100%;
  padding: 26px; text-align: center; box-shadow: var(--shadow-lg);
  border: 1px solid var(--line); max-height: 90vh; overflow: auto;
  animation: dc-pop .45s var(--back) both;
}
.modal h3 { font-size: 1.4rem; }
.copy-row { display: flex; gap: 8px; margin: 14px 0; }
.copy-row input { flex: 1; font-size: .85rem; }

/* ---------- Guest page: dropzone ---------- */
.dropzone {
  border: 1.5px dashed var(--line); border-radius: var(--radius);
  padding: 36px 18px; text-align: center; color: var(--soft);
  cursor: pointer; background: var(--chrome);
  transition: border-color .25s var(--out), background .25s var(--out), transform .25s var(--back);
}
.dropzone:hover { border-color: var(--acc); }
.dropzone.drag { border-color: var(--acc); background: var(--tint); transform: scale(1.01); }
.dropzone strong { font-family: var(--serif); font-size: 1.35rem; font-weight: 600; color: var(--ink); }

/* ---------- Gallery ---------- */
.gallery {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 12px; margin-top: 16px;
}
/* Tiles carry the video's Polaroid treatment: a white mount, a soft drop
   shadow, and the photo inset rather than bled to the edge. */
.tile {
  position: relative; border: 1px solid var(--line); padding: 6px 6px 26px; cursor: pointer;
  border-radius: 4px; background: var(--card); box-shadow: var(--shadow-sm);
  aspect-ratio: 1 / 1; display: block; overflow: hidden;
  transition: transform .3s var(--back), box-shadow .3s var(--out);
}
.tile:hover { transform: translateY(-3px); box-shadow: 0 10px 26px rgba(38, 33, 26, .22); }
.tile img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  border-radius: 2px; background: var(--pill);
}
/* Three corners, three marks, no collisions: the video badge top-right, the
   comment count bottom-right, and the selection tick top-left — which only
   exists in selection mode but must not land on either of the others. */
.tile .vid-mark, .tile .cmark {
  position: absolute; right: 12px; background: rgba(38, 33, 26, .62);
  color: #fff; font-size: .7rem; padding: 2px 8px; border-radius: 999px;
}
.tile .vid-mark { top: 12px; }
.tile .cmark { bottom: 32px; }
/* The caption sits on the mount below the photo, like a polaroid's white
   strip — not as a gradient scrim over the picture. */
.tile .fname {
  position: absolute; left: 6px; right: 6px; bottom: 0; height: 26px;
  display: flex; flex-direction: column; justify-content: center; gap: 0;
  background: var(--card); color: var(--ink);
  font-size: .7rem; line-height: 1.15; padding: 0 2px; text-align: left;
}
.tile .fname b {
  font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tile .fname i {
  font-style: normal; font-size: .64rem; color: var(--soft); white-space: nowrap;
}

/* ---------- Upload rows ---------- */
.upl-row { display: flex; align-items: center; gap: 10px; padding: 9px 0; font-size: .9rem; }
.upl-row .bar { flex: 1; height: 6px; background: var(--pill); border-radius: 999px; overflow: hidden; }
.upl-row .bar i { display: block; height: 100%; width: 0; background: var(--acc); transition: width .25s var(--out); }
.upl-row.done .bar i { background: var(--ok); }
.upl-row.fail .bar i { background: var(--danger); }
.upl-name { max-width: 40%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.spin {
  width: 26px; height: 26px; border: 2px solid var(--line); border-top-color: var(--acc);
  border-radius: 50%; animation: sp 1s linear infinite; margin: 20px auto;
}
@keyframes sp { to { transform: rotate(360deg); } }

/* ---------- Option groups (folder source / permission mode) ---------- */
.opt-group { display: flex; flex-direction: column; gap: 8px; margin: 6px 0 4px; }
.opt {
  position: relative; overflow: hidden;
  display: flex; align-items: flex-start; gap: 10px;
  padding: 13px 15px; border: 1px solid var(--line); border-radius: 8px;
  cursor: pointer; background: var(--card); font-weight: 400; line-height: 1.45;
  transition: border-color .25s var(--out), background .25s var(--out);
}
.opt:hover { border-color: var(--soft); }
.opt input { margin: 3px 0 0; flex-shrink: 0; accent-color: var(--acc); }
.opt:has(input:checked) { border-color: var(--acc); background: var(--tint); }
.opt .muted { font-size: .82rem; }
.qr-missing { font-size: .85rem; color: var(--soft); text-align: center; margin: 0; }

/* ---------- Viewer ----------
   Image and sidebar side by side on desktop; stacked on a phone so the
   comments stay reachable without the picture eating the screen. */
.viewer {
  position: fixed; inset: 0; z-index: 100; display: none;
  /* Opaque. At .94 the page behind showed through clearly against white,
     so the album title and dropzone text floated over the photo. */
  background: #1b1710;
  grid-template-columns: 1fr 340px;
}
.viewer.open { display: grid; animation: dc-fade .3s var(--out) both; }
.viewer__stage { display: flex; align-items: center; justify-content: center; padding: 24px; min-width: 0; }
.viewer__stage img, .viewer__stage video {
  max-width: 100%; max-height: 88vh; object-fit: contain;
  box-shadow: var(--shadow-lg); animation: dc-pop .5s var(--back) both;
}
.viewer__side {
  background: var(--card); display: flex; flex-direction: column;
  border-left: 1px solid var(--line); min-height: 0;
}
.viewer__meta { padding: 18px 16px; border-bottom: 1px solid var(--line); }
.viewer__name { font-family: var(--serif); font-size: 1.2rem; font-weight: 600; word-break: break-word; }
.viewer__by { font-size: .85rem; margin-top: 3px; color: var(--soft); }
.viewer__comments { flex: 1; overflow-y: auto; padding: 14px 16px; min-height: 0; }
.cmt { margin-bottom: 11px; line-height: 1.45; word-break: break-word; }
.cmt b { color: var(--ink); }
.viewer__compose { border-top: 1px solid var(--line); padding: 12px 16px; }
.viewer__compose textarea { resize: vertical; font: inherit; }
.emoji-row { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 8px; }
.emoji {
  position: relative; overflow: hidden;
  border: 1px solid var(--line); background: var(--card); border-radius: 8px;
  font-size: 1.15rem; line-height: 1; padding: 5px 7px; cursor: pointer;
  transition: background .2s var(--out), transform .2s var(--back);
}
.emoji:hover { background: var(--chrome); transform: scale(1.12); }
.viewer__close, .viewer__nav {
  position: absolute; background: rgba(38, 33, 26, .5); color: #fff; border: 0;
  cursor: pointer; border-radius: 50%; width: 44px; height: 44px; font-size: 1.6rem;
  line-height: 1; display: flex; align-items: center; justify-content: center;
  transition: background .25s var(--out), transform .25s var(--back);
  /* Must outrank .viewer__stage, which is positioned on mobile and comes
     later in the DOM — without this it paints over all three buttons and
     they cannot be tapped at all. */
  z-index: 3;
}
.viewer__close { top: 14px; right: 356px; }
.viewer__nav { top: 50%; transform: translateY(-50%); }
.viewer__prev { left: 14px; }
.viewer__next { right: 356px; }
.viewer__close:hover { background: rgba(38, 33, 26, .8); transform: scale(1.08); }
.viewer__nav:hover { background: rgba(38, 33, 26, .8); }

@media (max-width: 820px) {
  /* One scrollable column instead of fixed vh rows. Locking the comments
     into a 1fr row left them squeezed into a sliver, and the on-screen
     keyboard shrinks the viewport so vh-based rows collapse while typing. */
  .viewer { grid-template-columns: 1fr; }
  .viewer.open { display: block; overflow-y: auto; -webkit-overflow-scrolling: touch; }
  /* Reserve the space before the photo arrives. Without this the stage has
     no height until the image decodes, so the comment panel starts at the top
     of the screen and then jumps down — very obvious on a phone pulling a
     7 MB photo over mobile data. */
  .viewer__stage { padding: 8px; position: relative; min-height: 55vh; }
  .viewer__stage img, .viewer__stage video { max-height: 60vh; width: auto; }
  .viewer__side {
    border-left: 0; border-top: 1px solid var(--line);
    border-radius: 14px 14px 0 0; min-height: 45vh;
  }
  /* Let the page scroll rather than nesting a scroll area inside a scroll
     area — nested scrolling on touch is genuinely hard to operate. */
  .viewer__comments { overflow: visible; flex: none; }
  .viewer__compose { position: sticky; bottom: 0; background: var(--card); }
  .viewer__close { top: 10px; right: 10px; }
  .viewer__nav { top: 30vh; }
  .viewer__next { right: 10px; }
  .viewer__prev { left: 10px; }
}

/* ---------- Gallery toolbar / selection ---------- */
.gal-toolbar {
  display: flex; flex-wrap: wrap; gap: 12px; align-items: center;
  justify-content: space-between; margin-bottom: 14px;
}
.gal-sort { display: flex; align-items: center; gap: 8px; font-size: .9rem; }
.gal-sort select { width: auto; padding: 8px 11px; }
.gal-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* The tick only appears in selection mode, so a normal visitor never sees
   controls they cannot use. */
.tick { display: none; }
.gallery.selecting .tick {
  display: block; position: absolute; top: 12px; left: 12px;
  width: 22px; height: 22px; border-radius: 50%;
  border: 2px solid #fff; background: rgba(38, 33, 26, .35);
  box-shadow: 0 1px 4px rgba(38, 33, 26, .4);
}
.gallery.selecting .tick.on { background: var(--acc); border-color: #fff; }
.gallery.selecting .tick.on::after {
  content: ""; position: absolute; left: 6px; top: 2px;
  width: 5px; height: 10px; border: solid #fff; border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}
.gallery.selecting .tile { outline: 2px solid transparent; }
.gallery.selecting .tile:has(.tick.on) { outline-color: var(--acc); }

@media (max-width: 560px) {
  .gal-toolbar { flex-direction: column; align-items: stretch; }
  .gal-actions { justify-content: space-between; }
}

/* --- Owner photo picker (admin) --- */
.avapick { display: flex; align-items: center; gap: 14px; margin: 4px 0 2px; }
.avapick__slot {
  width: 72px; height: 72px; border-radius: 50%; flex: 0 0 auto;
  border: 1.5px dashed var(--line); background: var(--chrome); cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  color: var(--soft); font-size: 1.6rem; overflow: hidden; padding: 0;
  transition: border-color .25s var(--out), color .25s var(--out);
}
.avapick__slot:hover { border-color: var(--acc); color: var(--acc); }
.avapick__slot img { width: 100%; height: 100%; object-fit: cover; display: block; }
.avapick__hint { font-size: .85rem; color: var(--soft); }

/* --- Owner block (guest view) --- */
.owner { display: flex; align-items: center; gap: 16px; margin: 0 0 18px; }
.owner__ava {
  width: 64px; height: 64px; border-radius: 50%; object-fit: cover;
  flex: 0 0 auto; border: 2px solid var(--card); box-shadow: var(--shadow);
}
.owner__note { font-family: var(--serif); font-size: 1.3rem; line-height: 1.35; }

/* --- Bottom promo banner (guest view) ---
   The whole strip is the link, so the tap target is the full width on a
   phone rather than just the button. */
.promo {
  position: relative; overflow: hidden;
  display: flex; align-items: center; justify-content: space-between;
  gap: 18px; flex-wrap: wrap;
  margin: 30px 0 0; padding: 22px 26px;
  border-radius: var(--radius);
  background: var(--chrome);
  border: 1px solid var(--line);
  color: var(--ink); text-decoration: none;
  transition: box-shadow .25s var(--out), transform .25s var(--back), border-color .25s var(--out);
}
.promo:hover { box-shadow: var(--shadow); transform: translateY(-2px); border-color: var(--acc); }
.promo__text { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.promo__title { font-family: var(--serif); font-size: 1.35rem; font-weight: 600; }
.promo__sub { font-size: .9rem; color: var(--soft); }
.promo__cta {
  flex: 0 0 auto; white-space: nowrap;
  background: var(--btn-bg); color: var(--btn-fg);
  padding: 12px 22px; border-radius: 8px; font-size: .95rem; font-weight: 600;
  transition: background .25s var(--out);
}
.promo:hover .promo__cta { background: var(--acc); }
@media (max-width: 520px) {
  .promo { flex-direction: column; align-items: stretch; }
  .promo__cta { text-align: center; }
}

/* --- Photographer brand (admin header) ---
   Centred rather than tucked into the left corner: it is the only mark of
   whose tool this is, and off to one side it read as a stray back-link. */
.brand {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  text-decoration: none; color: var(--ink); margin: 4px 0 10px;
}
.brand__mark { width: 40px; height: 40px; object-fit: contain; flex: 0 0 auto; }
.brand__text { display: flex; flex-direction: column; line-height: 1.15; }
.brand__name { font-family: var(--serif); font-size: 1.05rem; font-weight: 600; }
.brand__sub { font-size: .72rem; color: var(--soft); letter-spacing: .16em; text-transform: uppercase; }

/* --- Photographer credit (guest page footer) ---
   Deliberately quiet: this is someone else's wedding, and the couple's own
   photo is already at the top of the page. */
.credit {
  display: flex; align-items: center; justify-content: center; gap: 12px;
  text-decoration: none; color: var(--ink);
  margin: 32px 0 0; padding-top: 20px;
  border-top: 1px solid var(--line);
}
.credit__mark { width: 32px; height: 32px; object-fit: contain; flex: 0 0 auto; }
.credit__text { display: flex; flex-direction: column; line-height: 1.25; }
.credit__name { font-family: var(--serif); font-size: 1rem; font-weight: 600; }
.credit__sub { font-size: .8rem; color: var(--soft); }
.credit:hover .credit__name { color: var(--acc); }

/* ==========================================================================
   Live slideshow (slideshow.php)
   ==========================================================================
   Built for a projector or a television across the room: dark ground so the
   photograph is the only light in the frame, type large enough to read from
   the back, and every control fading out of the way after a few idle seconds. */
.show-body { background: #12100c; overflow: hidden; }
.show-body .wrap { padding-top: 60px; }
/* The paper-ground line-art belongs on the album pages, not on a projector —
   there it is just two grey shapes floating over the photograph. */
.show-body::before, .show-body::after { display: none; }

.show { position: fixed; inset: 0; display: block; }
.show__stage { position: absolute; inset: 0; overflow: hidden; background: #12100c; }
.show__img {
  position: absolute; inset: 0; margin: auto;
  max-width: 100%; max-height: 100%; width: auto; height: auto;
  object-fit: contain;
  animation: show-in 1.1s var(--out) both, show-drift 14s linear both;
}
@keyframes show-in { from { opacity: 0; } to { opacity: 1; } }
/* Slow, barely-there push. Anything stronger reads as a wobble on a big
   screen, and a wobble is what people notice instead of the photograph. */
@keyframes show-drift {
  from { transform: scale(1.03); }
  to   { transform: scale(1.09); }
}

.show__caption {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 2;
  /* Wide side padding so a long name never runs under the control bar. */
  padding: 90px 200px 34px; text-align: center; pointer-events: none;
  background: linear-gradient(transparent, rgba(18, 16, 12, .78));
}
@media (max-width: 700px) {
  .show__caption { padding: 70px 20px 86px; }
  .show__bar { left: 50%; right: auto; transform: translateX(-50%); }
}
.show__by {
  font-family: var(--serif); font-style: italic; font-size: clamp(20px, 2.6vw, 34px);
  color: #fff; text-shadow: 0 2px 18px rgba(0, 0, 0, .6); min-height: 1.2em;
}
.show__album {
  margin-top: 8px; font-family: var(--sans); font-size: clamp(11px, 1vw, 14px);
  letter-spacing: .3em; text-transform: uppercase; color: var(--acc);
}

/* The holding card: what the room looks at before the first photo lands. */
.show__empty {
  position: absolute; inset: 0; z-index: 3;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 18px; text-align: center; padding: 24px;
  background: #12100c; color: #fff;
  transition: opacity .8s var(--out);
}
.show__empty.is-gone { opacity: 0; pointer-events: none; }
.show__empty-mark {
  width: 78px; height: 78px; border-radius: 50%; border: 1.5px solid rgba(255, 255, 255, .55);
  display: flex; align-items: center; justify-content: center;
  font-family: var(--serif); font-weight: 600; font-size: 32px; letter-spacing: .03em;
}
.show__empty h1 { font-size: clamp(30px, 5vw, 66px); margin: 0; font-weight: 600; }
.show__rule { width: 90px; height: 1.5px; background: var(--acc); }
.show__empty p { color: rgba(255, 255, 255, .6); font-size: clamp(14px, 1.6vw, 20px); margin: 0; }

/* Bottom-right, not centred: the caption is centred, and the two were
   sitting on top of each other. */
.show__bar {
  position: absolute; right: 26px; bottom: 26px; z-index: 4;
  display: flex; align-items: center; gap: 10px;
  padding: 9px 14px; border-radius: 999px;
  background: rgba(18, 16, 12, .72); border: 1px solid rgba(255, 255, 255, .16);
  transition: opacity .5s var(--out);
}
.show__btn {
  min-width: 40px; height: 40px; padding: 0 12px; border-radius: 999px;
  background: none; border: 0; color: #fff; font-size: 20px; line-height: 1; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  transition: background .2s var(--out);
}
.show__btn:hover { background: rgba(255, 255, 255, .14); }
.show__btn--wide { font-family: var(--sans); font-size: 13px; letter-spacing: .04em; }
.show__count {
  font-family: var(--sans); font-size: 12px; letter-spacing: .14em;
  color: rgba(255, 255, 255, .5); padding: 0 8px; white-space: nowrap;
}

/* Four seconds of no input and the room is left with just the photograph.
   This ends up on a projector for six hours; nobody is coming back to hide
   a toolbar by hand. */
.show-body.is-idle { cursor: none; }
.show-body.is-idle .show__bar { opacity: 0; pointer-events: none; }

@media (prefers-reduced-motion: reduce) {
  .show__img { animation: show-in .01s both; }
}

/* ---------- Moderation ---------- */
/* A photo the owner has not released yet. Only they and its uploader ever see
   the tile at all — this marks it so neither mistakes it for published. */
.tile.is-pending { opacity: .82; }
.tile.is-pending::before {
  content: "Laukia"; position: absolute; left: 6px; top: 6px; z-index: 2;
  background: var(--acc); color: #fff; font-size: .64rem; font-weight: 600;
  letter-spacing: .06em; text-transform: uppercase; padding: 3px 8px; border-radius: 999px;
}
.gallery.selecting .tile.is-pending::before { left: 34px; }
.mod-bar {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  background: #fbf7f1; border: 1px solid var(--acc); border-radius: 10px;
  padding: 12px 16px; margin-bottom: 14px;
}
.mod-bar__text { flex: 1; min-width: 200px; font-size: .9rem; }
.mod-bar__text b { font-family: var(--serif); font-size: 1.05rem; }

/* ==========================================================================
   Motion — the video's three primitives
   ==========================================================================
   enter  fade up 24px on easeOutCubic       — cards, page furniture
   pop    scale .85 → 1 on easeOutBack       — tiles, modals, the open photo
   draw   a rule growing from nothing        — the gold rules under titles

   The hidden start states are gated on `html.js`, set by an inline snippet in
   each page's <head>, so a browser without JavaScript renders the finished
   page and nothing can be left invisible. motion.js stamps --i on each item
   for the stagger; the fallbacks below keep everything visible if it never
   arrives. */

@keyframes dc-enter { from { opacity: 0; transform: translateY(24px); } to { opacity: 1; transform: none; } }
/* Opacity reaches 1 at the halfway point, as in the video's pop(), so the
   overshoot happens on an already-solid element. */
@keyframes dc-pop   { from { opacity: 0; transform: scale(.85); } 50% { opacity: 1; } to { opacity: 1; transform: none; } }
@keyframes dc-rule  { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@keyframes dc-fade  { from { opacity: 0; } to { opacity: 1; } }

@media (prefers-reduced-motion: no-preference) {
  .js .card, .js .owner, .js .credit, .js .promo, .js header.appbar {
    animation: dc-enter .7s var(--out) both;
    animation-delay: calc(var(--i, 0) * .09s);
  }
  .js .tile {
    animation: dc-pop .5s var(--back) both;
    animation-delay: calc(var(--i, 0) * .045s);
  }
  .js .link-item {
    animation: dc-enter .6s var(--out) both;
    animation-delay: calc(var(--i, 0) * .07s);
  }
}

/* If JavaScript never runs, nothing above ever applies — but a page that got
   as far as html.js and then lost motion.js still needs a way out. */
.anim-fallback .card, .anim-fallback .tile, .anim-fallback .link-item,
.anim-fallback .owner, .anim-fallback .credit, .anim-fallback .promo,
.anim-fallback header.appbar { animation: none; opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
  .ring { display: none; }
}

/* ==========================================================================
   Theme toggle
   ========================================================================== */
.theme-toggle {
  flex: 0 0 auto; width: 40px; height: 40px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--line); border-radius: 50%;
  background: var(--card); color: var(--soft); cursor: pointer;
  transition: color .25s var(--out), border-color .25s var(--out), transform .25s var(--back);
}
.theme-toggle:hover { color: var(--acc); border-color: var(--acc); transform: scale(1.06); }
.theme-toggle svg { width: 19px; height: 19px; display: block; }
/* One button, two glyphs: show the theme it will switch to, not the one
   already on screen. */
.theme-toggle .i-sun  { display: none; }
:root[data-theme="dark"] .theme-toggle .i-sun  { display: block; }
:root[data-theme="dark"] .theme-toggle .i-moon { display: none; }

/* ==========================================================================
   Gallery refinements
   ==========================================================================
   The polaroid mount stays — it is the app's whole visual idea. What changes
   is how it behaves under a thumb: two comfortable columns instead of three
   cramped ones, a photo that fades in rather than snapping from grey, and a
   press state, because on touch there is no hover to confirm the tap. */
.gallery { gap: 14px; }
.tile { border-radius: 8px; }
.tile img {
  border-radius: 4px;
  opacity: 0; transition: opacity .45s var(--out);
}
.tile img.ready, .anim-fallback .tile img { opacity: 1; }
.tile:active { transform: scale(.975); }
@media (hover: none) {
  /* Lifting on tap leaves the tile hovering after the finger is gone. */
  .tile:hover { transform: none; box-shadow: var(--shadow-sm); }
}
@media (max-width: 620px) {
  .gallery { grid-template-columns: repeat(2, 1fr); gap: 12px; }
  .tile { padding: 5px 5px 24px; }
}

/* ==========================================================================
   Immersive viewer
   ==========================================================================
   "Full screen" here means the photograph and nothing else: no comment rail,
   no filename, no compose box. The class does the work rather than :fullscreen
   alone, because iOS Safari will not put an arbitrary element into real
   fullscreen — there the class still delivers the same view, just inside the
   browser chrome. */
.viewer__tools { position: absolute; top: 14px; left: 14px; z-index: 3; display: flex; gap: 8px; }
.viewer__tool {
  background: rgba(38, 33, 26, .5); color: #fff; border: 0; cursor: pointer;
  border-radius: 999px; height: 44px; padding: 0 16px;
  display: inline-flex; align-items: center; gap: 8px;
  font: inherit; font-size: .85rem; font-weight: 600;
  transition: background .25s var(--out);
}
.viewer__tool:hover { background: rgba(38, 33, 26, .8); }
.viewer__tool svg { width: 17px; height: 17px; }
.viewer__tool .i-exit { display: none; }
.viewer.is-immersive .viewer__tool .i-exit { display: block; }
.viewer.is-immersive .viewer__tool .i-enter { display: none; }

.viewer.is-immersive { grid-template-columns: 1fr; background: #0d0b08; }
.viewer.is-immersive .viewer__side { display: none; }
.viewer.is-immersive .viewer__stage { padding: 0; }
.viewer.is-immersive .viewer__stage img,
.viewer.is-immersive .viewer__stage video {
  max-width: 100vw; max-height: 100vh; box-shadow: none;
}
.viewer.is-immersive .viewer__close { top: 14px; right: 14px; }
.viewer.is-immersive .viewer__next { right: 14px; }
.viewer.is-immersive .viewer__prev { left: 14px; }
/* The controls step back once the photo has the screen to itself, and come
   back on any pointer movement (handled in share.php). */
.viewer.is-immersive.idle .viewer__tools,
.viewer.is-immersive.idle .viewer__close,
.viewer.is-immersive.idle .viewer__nav { opacity: 0; pointer-events: none; }
.viewer__tools, .viewer__close, .viewer__nav { transition: opacity .4s var(--out); }

@media (max-width: 820px) {
  /* On a phone the normal viewer is one scrolling column; immersive has to
     undo that and pin the photo to the screen. */
  .viewer.is-immersive.open { display: flex; align-items: center; justify-content: center; overflow: hidden; }
  .viewer.is-immersive .viewer__stage { min-height: 100vh; width: 100%; }
  .viewer.is-immersive .viewer__stage img,
  .viewer.is-immersive .viewer__stage video { max-height: 100vh; width: auto; }
  .viewer.is-immersive .viewer__nav { top: 50%; }
  .viewer__tools { top: 10px; left: 10px; }
  .viewer__tool { height: 40px; padding: 0 13px; font-size: .8rem; }
}

/* The logo mark is a flat navy monogram on transparency — on the dark ground
   it all but disappears. Flattening it to a white silhouette keeps the shape
   and gives it back its contrast, without needing a second image file. */
:root[data-theme="dark"] .brand__mark,
:root[data-theme="dark"] .credit__mark { filter: brightness(0) invert(1); }

/* --- Privacy link (app pages) ---
   Quiet and last: it has to be findable from the two places this app actually
   collects anything — a guest's name and photographs on the album page, a
   Google account and its refresh token on the admin page — without competing
   with the asks above it. */
.applaw { text-align: center; margin: 26px 0 0; font-size: .82rem; }
.applaw a { color: var(--soft); text-decoration: none; border-bottom: 1px solid var(--line); }
.applaw a:hover { color: var(--acc); border-bottom-color: var(--acc); }


/* ---------- Sign-off ----------
   The same closing block the guest-gallery page uses: a face, the name, one
   paragraph, one button. Replaces the second .promo card — a guest who read
   that page before scanning the code should meet the same sentence here,
   rather than a differently-worded advert. */
.signoff {
  margin: 34px 0 0; padding: 30px 26px;
  text-align: center;
  border-top: 1px solid var(--line);
}
.signoff__ava {
  display: block; margin: 0 auto 18px;
  width: 104px; height: 104px; border-radius: 50%;
  /* The file is already cropped to the face — nothing left for cover to trim. */
  object-fit: cover;
  box-shadow: 0 6px 18px rgba(38, 33, 26, 0.18);
}
.signoff__name {
  font-family: var(--serif); font-size: 1.7rem; font-weight: 600; margin: 0;
}
.signoff__text {
  max-width: 560px; margin: 16px auto 0;
  color: var(--soft); font-size: .95rem; line-height: 1.7;
}
.signoff__cta { margin: 24px 0 0; }
.signoff__links {
  display: flex; justify-content: center; gap: 22px; flex-wrap: wrap;
  margin: 22px 0 0; font-size: .9rem;
}
.signoff__links a { color: var(--soft); text-decoration: none; border-bottom: 1px solid var(--line); }
.signoff__links a:hover { color: var(--acc); border-bottom-color: var(--acc); }

@media (max-width: 700px) {
  .signoff { padding: 26px 4px; }
  .signoff__ava { width: 86px; height: 86px; }
}


/* ---------- Slideshow: the way out ----------
   Set apart from the transport controls beside it: this one leaves. */
.show__btn--exit {
  text-decoration: none; font-size: 24px;
  margin-left: 4px; border-left: 1px solid rgba(255, 255, 255, .18);
  border-radius: 0 999px 999px 0; padding-left: 14px;
}

/* ---------- Viewer: nothing written over the photograph ----------
   The name and the uploader line are emptied rather than removed, because the
   download and delete buttons are inserted after those nodes. Empty, they
   would still hold a line of height each and push the comments down. */
.viewer__name:empty, .viewer__by:empty { display: none; }
/* With both gone the meta row is only the buttons; when there are no buttons
   either, it is nothing at all and should not draw its divider. */
.viewer__meta:not(:has(.btn)) { display: none; }
.viewer__meta { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

/* Social marks on the sign-off. Drawn in currentColor so they follow the
   theme, and sized by the SVG box rather than a font. */
.signoff__social {
  display: flex; justify-content: center; gap: 14px; margin: 18px 0 0;
}
.signoff__social a {
  display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; border-radius: 50%;
  border: 1px solid var(--line); color: var(--soft);
  transition: color .25s var(--out), border-color .25s var(--out), transform .25s var(--back);
}
.signoff__social a:hover {
  color: var(--acc); border-color: var(--acc); transform: translateY(-2px);
}
.signoff__social svg { width: 19px; height: 19px; display: block; }
