/* ============================================================
   RevelPics design tokens

   The palette below is the DEFAULT only. When an event has custom
   colours, theme.js derives every token from the host's background +
   highlight pair and writes them as inline custom properties on
   <html>, which outranks anything in this file.

   So: this file defines the fallback look and all the non-colour
   tokens (type, spacing, radii). Colour logic lives in theme.js.
   ============================================================ */

:root{
  /* ---- fallback palette (warm linen) ---- */
  --bg:#FBF8F4;          --bg-tint:#F5EFE7;
  --panel:#FFFFFF;       --panel-2:#F4EFE8;
  --line:#E8E0D5;        --line-soft:#F0EAE1;
  --ink:#2E2724;         --dim:#7C7168;
  /* The brand rose, sampled from the wordmark artwork itself (#CE6D7C), so the
     eyebrow, headings, links and buttons are the same colour as the logo.
     --accent-ink is the near-black that theme.js derives for this accent, NOT
     white: white on this pink is only 3.4:1, while #101012 on it is 5.6:1. Same
     values the album theme engine produces, so the storefront and a default
     album look identical and the button label stays readable.

     --accent stays the exact wordmark rose because that is what fills use.
     --accent-text is the sibling used wherever the accent is written as TEXT or
     drawn as a small icon (links, eyebrows, heart glyphs, tags). The rose as
     small text is 3.25:1 on this background, under AA's 4.5, so the darker
     sibling is used there instead. #C1475A is the exact value theme.js derives
     for this accent on this background (4.60:1), so the storefront and an album
     whose host picked the default rose render identically rather than merely
     similarly, which is the same reason --accent-ink is the derived near-black. */
  --accent:#CE6D7C;      --accent-hover:#C55264;
  --accent-ink:#101012;  --accent-soft:#F5E5E3;
  --accent-text:#C1475A;
  --ok:#4F7A5A;          --danger:#A8443A;
  --shadow:0 1px 2px rgba(46,39,36,.04),0 10px 28px rgba(46,39,36,.07);
  --shadow-sm:0 1px 2px rgba(46,39,36,.05);
  --qr-bg:#FFFFFF;

  /* ---- type ---- */
  --font-display: "Iowan Old Style","Palatino Linotype",Palatino,"Book Antiqua",
                  Georgia,ui-serif,serif;
  --font-body: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,
               "Helvetica Neue",Arial,sans-serif;

  --t-xs:.75rem; --t-sm:.875rem; --t-base:1rem; --t-lg:1.125rem;
  --t-xl:1.375rem; --t-2xl:1.75rem; --t-3xl:2.25rem;

  /* ---- space ---- */
  --s-1:.25rem; --s-2:.5rem; --s-3:.75rem; --s-4:1rem;
  --s-5:1.5rem; --s-6:2rem; --s-7:3rem;

  --radius:16px; --radius-sm:10px; --radius-pill:99px;
  --maxw:720px;
}

/* ============================================================ base */
*{box-sizing:border-box}
html,body{margin:0;padding:0}
html{-webkit-text-size-adjust:100%}
body{
  background:var(--bg);
  color:var(--ink);
  font:var(--t-base)/1.6 var(--font-body);
  -webkit-font-smoothing:antialiased;
  min-height:100vh;
}
.wrap{max-width:var(--maxw);margin:0 auto;padding:var(--s-5) var(--s-4) var(--s-7)}
.mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}

h1{
  font-family:var(--font-display);
  font-size:var(--t-3xl); font-weight:400; letter-spacing:-.015em;
  line-height:1.15; margin:0 0 var(--s-2);
}
h2{
  font-size:var(--t-xs); margin:var(--s-6) 0 var(--s-3);
  color:var(--dim); text-transform:uppercase; letter-spacing:.1em;
  font-weight:700;
}
p{margin:0 0 var(--s-3)}
a{color:var(--accent-text);text-underline-offset:2px}
.sub{color:var(--dim);font-size:var(--t-lg);margin:0 0 var(--s-5);line-height:1.5}
hr{border:0;border-top:1px solid var(--line-soft);margin:var(--s-6) 0}

/* ============================================================ cards */
.card{
  background:var(--panel);
  border:1px solid var(--line);
  border-radius:var(--radius);
  padding:var(--s-5);
  margin:0 0 var(--s-4);
  box-shadow:var(--shadow);
}
.card.flat{box-shadow:var(--shadow-sm)}

/* ============================================================ forms */
label{
  display:block;font-size:var(--t-sm);color:var(--dim);
  margin:var(--s-4) 0 var(--s-2);font-weight:600;
}
input[type=text],input[type=email],input[type=date],input[type=password],
input[type=number],input[type=file],select{
  width:100%;padding:.8rem .95rem;
  background:var(--panel-2);
  border:1px solid var(--line);
  border-radius:var(--radius-sm);
  color:var(--ink);font-size:var(--t-base);font-family:inherit;
  transition:border-color .15s,box-shadow .15s;
}
input::placeholder{color:var(--dim);opacity:.75}
input:focus,select:focus{
  outline:none;border-color:var(--accent);
  box-shadow:0 0 0 3px var(--accent-soft);
}
.check{display:flex;align-items:center;gap:var(--s-3);margin:var(--s-4) 0;
       font-size:var(--t-base);color:var(--ink)}
.check input{width:18px;height:18px;accent-color:var(--accent);flex:none}

button,.btn{
  display:inline-block;width:100%;
  padding:.9rem var(--s-5);margin-top:var(--s-2);
  background:var(--accent);color:var(--accent-ink);
  border:0;border-radius:var(--radius-sm);
  font-size:var(--t-base);font-weight:650;font-family:inherit;
  cursor:pointer;text-align:center;text-decoration:none;
  transition:background .15s,transform .06s;
}
button:hover,.btn:hover{background:var(--accent-hover);text-decoration:none}
button:active,.btn:active{transform:translateY(1px)}
button:disabled{opacity:.45;cursor:default}
.btn.ghost{background:transparent;color:var(--ink);border:1px solid var(--line)}
.btn.ghost:hover{background:var(--panel-2);border-color:var(--dim)}
.btn.small{width:auto;padding:.6rem var(--s-4);font-size:var(--t-sm)}
/* The dashboard's empty state. Its whole job is to name one action, so the button is
   centred at a deliberate width: button and .btn default to width:100%, which inside a
   card would stretch it edge to edge and make the only clickable thing look like a
   banner rather than a choice. */
.emptycta{text-align:center}
.emptycta p{margin:0 0 var(--s-5);color:var(--dim);line-height:1.5}
.emptycta .btn{width:auto;margin:0 auto;min-width:220px}
.row{display:flex;gap:var(--s-2);flex-wrap:wrap}

/* ============================================================ progress */
.bar{
  height:10px;background:var(--panel-2);border-radius:var(--radius-pill);
  overflow:hidden;margin:var(--s-5) 0 var(--s-2);
}
.bar > i{display:block;height:100%;width:0;background:var(--accent);
         border-radius:var(--radius-pill);transition:width .25s ease}
.rowstat{display:flex;justify-content:space-between;font-size:var(--t-sm);color:var(--dim)}

/* ============================================================ gallery */
.grid{
  display:grid;grid-template-columns:repeat(auto-fill,minmax(108px,1fr));
  gap:var(--s-2);margin-top:var(--s-3);
}
.grid .cell{
  position:relative;aspect-ratio:1;background:var(--panel-2);
  border-radius:var(--radius-sm);overflow:hidden;
  border:1px solid var(--line-soft);
}
.grid .cell img,.grid .cell video{width:100%;height:100%;object-fit:cover;display:block}
.grid .cell .badge{
  position:absolute;left:0;right:0;bottom:0;padding:.3rem .45rem;
  font-size:.68rem;line-height:1.3;
  background:linear-gradient(transparent,rgba(20,16,14,.82));
  color:#fff;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.grid .cell.pending{
  display:flex;align-items:center;justify-content:center;
  color:var(--dim);font-size:.7rem;
}
.grid .cell.failed{border-color:var(--danger)}
.grid .cell.failed::after{
  content:"";position:absolute;inset:0;background:var(--danger);opacity:.12;pointer-events:none;
}
.grid a.cell{display:block;text-decoration:none;color:inherit}
.grid a.cell:hover{border-color:var(--accent)}
.grid .cell.isvideo .badge::first-letter{font-size:.7rem}
/* A video tile has to say so at a glance, from the middle of the frame. The
   poster is a still, so without this a clip and a photo look identical in the
   grid. Decorative only: it takes no clicks, because the whole tile is one link
   into the viewer, and the badge keeps its own "▶ " prefix as the text version
   for anyone who cannot see the circle. `:not(.failed)` keeps a broken upload's
   red wash (below) as the only ::after on that cell. */
.grid .cell.isvideo:not(.failed)::after{
  content:"";position:absolute;top:50%;left:50%;width:44px;height:44px;
  margin:-22px 0 0 -22px;border-radius:50%;pointer-events:none;
  border:1px solid rgba(255,255,255,.28);
  background:rgba(14,12,11,.52) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23fff'%3E%3Cpath d='M9 5.6v12.8l10-6.4z'/%3E%3C/svg%3E") center/15px 15px no-repeat;
  -webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);
  box-shadow:0 2px 10px rgba(18,12,10,.35);
}
.grid .cell.iscover{outline:2px solid var(--accent);outline-offset:2px}

/* cover picker, shown on admin tiles only */
.setcover{
  position:absolute;top:5px;right:5px;width:auto;margin:0;
  padding:.22rem .5rem;font-size:.62rem;font-weight:700;line-height:1.35;
  background:rgba(14,12,11,.62);color:#fff;border:0;border-radius:6px;
  -webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);cursor:pointer;opacity:0;transition:opacity .13s;
}
.grid .cell:hover .setcover,.grid .cell:focus-within .setcover{opacity:1}
@media (hover:none){.setcover{opacity:1}}

/* ========================================================== moderation */
.grid .cell.ispending{outline:2px dashed var(--accent);outline-offset:2px}
.pbadge{
  position:absolute;top:5px;left:5px;padding:.22rem .5rem;font-size:.62rem;
  font-weight:700;background:var(--accent);color:var(--accent-ink);
  border-radius:6px;letter-spacing:.02em;
}
.tileacts{
  position:absolute;left:0;right:0;bottom:0;display:flex;gap:2px;
  padding:3px;background:linear-gradient(transparent,rgba(14,12,11,.72));
  opacity:0;transition:opacity .13s;
}
.grid .cell:hover .tileacts,.grid .cell:focus-within .tileacts{opacity:1}
@media (hover:none){.tileacts{opacity:1}}
.tilebtn{
  flex:1;width:auto;margin:0;padding:.3rem .2rem;font-size:.6rem;font-weight:700;
  line-height:1.3;border:0;border-radius:5px;cursor:pointer;
  background:rgba(255,255,255,.9);color:#1a1a1a;
}
.tilebtn.good{background:#2f8f5b;color:#fff}
.tilebtn.danger{background:#b4352c;color:#fff}
.tilebtn:disabled{opacity:.55;cursor:default}

/* ============================================================ cover hero */
.hero{
  position:relative;margin:0 0 var(--s-4);
  border-radius:var(--radius);overflow:hidden;
  border:1px solid var(--line);box-shadow:var(--shadow);
  background:var(--panel-2);
}
.hero img{width:100%;height:min(44vh,300px);object-fit:cover;display:block}
.hero .cap{
  position:absolute;left:0;right:0;bottom:0;
  padding:var(--s-6) var(--s-5) var(--s-4);
  background:linear-gradient(transparent,rgba(0,0,0,.66));
}
.hero .cap h1,.hero .cap h2{color:#fff;margin:0;text-shadow:0 1px 14px rgba(0,0,0,.45)}
.hero .cap h1{font-size:var(--t-3xl)}
.hero .cap p{
  margin:.2rem 0 0;color:rgba(255,255,255,.88);
  font-size:var(--t-sm);text-shadow:0 1px 10px rgba(0,0,0,.5);
}

/* ============================================================ misc */
.hint{font-size:var(--t-sm);color:var(--dim);margin-top:var(--s-2);line-height:1.5}
.pill{
  display:inline-block;padding:.15rem .6rem;border-radius:var(--radius-pill);
  background:var(--panel-2);border:1px solid var(--line);
  font-size:var(--t-xs);color:var(--dim);margin-right:var(--s-2);
}
.pill.ok{color:var(--ok);border-color:var(--ok)}
.pill.bad{color:var(--danger);border-color:var(--danger)}
.err{color:var(--danger);font-size:var(--t-sm);margin-top:var(--s-3)}
.okmsg{color:var(--ok);font-size:var(--t-sm);margin-top:var(--s-3)}
.foot{color:var(--dim);font-size:var(--t-xs);text-align:center;margin-top:var(--s-7)}

/* The brand lockup that leads the shared footer, so every page that carries the
   policy links also carries a way back to the storefront. Mark and wordmark as one
   link, the same arrangement the top bar uses. It is styled HERE rather than in
   luxe.css because every page includes the shared footer and only the storefront
   and the guest album load luxe.css, so the pages that most needed the way home
   were the ones with no rule for it.

   The policy line is pulled up tighter when the lockup leads it: they are one
   block, and .legal-foot's own --s-7 top margin is meant for a single trailing
   line of text, not for a logo sitting above it. */
.brandfoot{margin:var(--s-6) auto 0;text-align:center}
.brandfoot .brandlink{display:inline-flex;align-items:center;gap:10px;
  text-decoration:none;opacity:.9;transition:opacity .18s}
.brandfoot .brandlink:hover{opacity:.72}
.brandfoot .brandlink:focus-visible{outline:2px solid var(--accent);outline-offset:3px}
.brandfoot .mark-img{display:block;width:26px;height:24px}
.brandfoot .wordmark-img{display:block;width:132px;height:auto}
.brandfoot + .legal-foot{margin-top:var(--s-3)}

/* The shared legal footer is two lines: the policy links, then who we are and who
   took the money. They read as one block, so the second sits tight under the
   first. .foot's own top margin is meant for a single trailing line. */
.legal-foot{margin-top:var(--s-7)}
.legal-foot + .legal-who{margin-top:4px}

/* ============================================================ qr + links */
.qrbox{
  background:var(--qr-bg);padding:var(--s-3);border-radius:var(--radius);
  display:inline-block;line-height:0;box-shadow:var(--shadow-sm);
}
.qrbox svg,.qrbox img{width:210px;height:210px;display:block}
.linkbox{display:flex;gap:var(--s-2);align-items:center;margin-top:var(--s-3)}
.linkbox input{font-size:var(--t-sm)}

/* ============================================================ stats */
.stats{display:flex;gap:var(--s-6);flex-wrap:wrap}
.stat b{
  display:block;font-family:var(--font-display);
  font-size:var(--t-3xl);font-weight:400;line-height:1.1;
}
.stat span{
  font-size:var(--t-xs);color:var(--dim);
  text-transform:uppercase;letter-spacing:.08em;font-weight:600;
}

/* ============================================================ log */
#log{
  font:var(--t-xs)/1.6 ui-monospace,SFMono-Regular,Menlo,monospace;
  color:var(--dim);white-space:pre-wrap;margin-top:var(--s-4);
  max-height:210px;overflow:auto;
}

/* ============================================================ theme picker */
.themepick{position:relative;display:flex;justify-content:flex-start;
           margin:0 0 var(--s-1)}
.themebtn{
  width:auto;margin:0;padding:.5rem .9rem;font-size:var(--t-sm);font-weight:600;
  display:inline-flex;align-items:center;gap:.5rem;
  background:var(--panel);color:var(--ink);
  border:1px solid var(--line);border-radius:var(--radius-pill);
  box-shadow:var(--shadow-sm);
}
.themebtn:hover{background:var(--panel-2);border-color:var(--dim)}
.themebtn .dot{
  width:14px;height:14px;border-radius:50%;display:inline-block;flex:none;
  border:1px solid var(--line);background:var(--accent);
}
.themepanel{
  position:absolute;top:calc(100% + 8px);left:0;z-index:80;
  width:min(342px,calc(100vw - 28px));
  background:var(--panel);border:1px solid var(--line);
  border-radius:var(--radius);box-shadow:var(--shadow);
  padding:var(--s-4);text-align:left;
}
.thgroup{margin-bottom:var(--s-4)}
.thlabel{
  font-size:var(--t-xs);text-transform:uppercase;letter-spacing:.08em;
  color:var(--dim);font-weight:700;margin-bottom:var(--s-2);
}
.swrow{display:flex;flex-wrap:wrap;gap:6px;align-items:center}
.swrow .sw{
  width:26px;height:26px;min-width:26px;padding:0;margin:0;flex:none;
  border-radius:8px;border:1px solid var(--line);box-shadow:none;cursor:pointer;
  transition:transform .1s;
}
.swrow .sw:hover{transform:scale(1.14);background-image:none}
.swrow .sw.on{outline:2px solid var(--accent);outline-offset:2px}
/* ------------------------------------------------ presets, preview, picker
   The presets lead, as named cards with a miniature of the guest page inside
   each: a host wants a mood ("Blush", "Midnight"), not a hex code. The two grids
   of 18 swatches and the two native <input type="color"> boxes that used to be
   the whole panel now sit behind the Customize toggle. */
.thpresets{display:grid;grid-template-columns:repeat(3,1fr);gap:7px}
.thcard{
  width:auto;margin:0;padding:6px;background:var(--panel);
  border:1px solid var(--line);border-radius:12px;cursor:pointer;box-shadow:none;
  display:flex;flex-direction:column;gap:5px;align-items:stretch;
  transition:transform .1s var(--lux-ease),border-color .1s;
}
.thcard:hover{background:var(--panel);border-color:var(--dim);transform:translateY(-1px)}
.thcard.on{border-color:var(--accent);box-shadow:0 0 0 2px var(--accent-soft)}
.tpn{font-size:.68rem;font-weight:700;color:var(--ink);text-align:center;line-height:1}
.thpv{
  display:block;width:100%;height:52px;padding:5px;overflow:hidden;
  border:1px solid var(--line);border-radius:8px;
}
.thpv i{display:block;border-style:none}
.tpv-panel{height:34%;border:1px solid transparent;border-radius:3px;margin-bottom:4px}
.tpv-txt{height:3px;width:72%;border-radius:2px;opacity:.75;margin-bottom:3px}
.tpv-link{height:3px;width:44%;border-radius:2px;margin-bottom:5px}
.tpv-btn{
  font-size:.5rem;font-style:normal;font-weight:700;line-height:1.3;
  width:max-content;padding:2px 6px;border-radius:99px;
}
.thpv.big{height:96px;padding:8px}
.thpv.big .tpv-btn{font-size:.65rem;padding:4px 9px}
.thpv.big .tpv-txt{height:4px;margin-bottom:4px}
.thpv.big .tpv-link{height:4px;margin-bottom:7px}
.thlive{margin-top:var(--s-1)}
.thtoggle{
  width:100%;margin:0 0 var(--s-3);padding:.5rem .9rem;
  font-size:var(--t-sm);font-weight:600;box-shadow:none;
  background:var(--panel-2);color:var(--ink);
  border:1px solid var(--line);border-radius:10px;
}
.thtoggle:hover{background:var(--panel);border-color:var(--dim)}
.thhex{
  width:100%;margin:0 0 var(--s-2);
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:var(--t-sm);
}
.thpick{display:block;margin-bottom:var(--s-2)}
.slsq{
  position:relative;width:100%;height:104px;border-radius:9px;
  border:1px solid var(--line);cursor:crosshair;touch-action:none;
  background:
    linear-gradient(to top,#000,rgba(0,0,0,0)),
    linear-gradient(to right,#fff,rgba(255,255,255,0)),
    hsl(var(--slh,0),100%,50%);
}
.slknob{
  position:absolute;width:15px;height:15px;margin:-7.5px 0 0 -7.5px;
  border-radius:50%;border:2px solid #fff;
  box-shadow:0 0 0 1px rgba(0,0,0,.4),0 1px 3px rgba(0,0,0,.35);
  pointer-events:none;
}
.thhue{
  width:100%;height:14px;margin:9px 0 0;padding:0;
  -webkit-appearance:none;appearance:none;border:0;border-radius:99px;
  background:linear-gradient(to right,#f00,#ff0,#0f0,#0ff,#00f,#f0f,#f00);
}
.thhue::-webkit-slider-thumb{
  -webkit-appearance:none;width:18px;height:18px;border-radius:50%;
  background:#fff;border:1px solid var(--line);box-shadow:var(--shadow-sm);
  cursor:pointer;
}
.thhue::-moz-range-thumb{
  width:18px;height:18px;border-radius:50%;background:#fff;
  border:1px solid var(--line);box-shadow:var(--shadow-sm);cursor:pointer;
}
/* A phone finger is not a mouse pointer: the quick swatches grow on touch. */
@media (hover:none){
  .swrow .sw{width:34px;height:34px;min-width:34px;border-radius:10px}
}
.thfoot{display:flex;align-items:center;gap:var(--s-3);flex-wrap:wrap;
        margin-top:var(--s-4)}
.thstatus{font-size:var(--t-xs);color:var(--ok)}
.thcontrast{margin-top:var(--s-2)}
.thcontrast.warn{color:var(--danger)}

/* ============================================================ accounts */
.hostbar{
  display:flex;align-items:center;justify-content:space-between;gap:var(--s-3);
  padding:var(--s-2) 0 var(--s-4);font-size:var(--t-sm);color:var(--dim);
  flex-wrap:wrap;
}
.hostbar a{font-weight:600}

/* ============================================================ payments */
/* The plan and its end date. One line at the top of the dashboard, where the
   question a host opens it to answer is, instead of a card. It also carries the
   deletion date, which used to be a Settings hint the host had to scroll to. */
.planline{margin:0 0 var(--s-3);font-size:var(--t-sm);color:var(--dim)}
.planline b{color:var(--ink);font-weight:700}
.payline{margin:0 0 var(--s-3);font-size:var(--t-md);line-height:1.55}
.payline b{font-weight:700}
.ok{
  margin:0;padding:.7rem .9rem;border-radius:var(--radius-sm);
  background:var(--accent-soft);color:var(--ink);
  font-weight:600;font-size:var(--t-sm);line-height:1.5;
}

/* ============================================================== legal */
.legal{max-width:68ch}
.legal h2{font-size:var(--t-lg);margin:var(--s-6) 0 var(--s-2)}
.legal h1{margin-bottom:var(--s-1)}
.legal p,.legal li{line-height:1.72;font-size:var(--t-md,1rem)}
.legal p{margin:0 0 var(--s-3)}
.legal ul{margin:0 0 var(--s-3);padding-left:1.2rem}
.legal li{margin-bottom:var(--s-2)}
.legal b{font-weight:700}
.legal .card p{margin:0;color:var(--dim);font-size:var(--t-sm)}
.eventlist{display:flex;flex-direction:column;gap:var(--s-2);margin-top:var(--s-3)}
.eventrow{
  display:flex;align-items:center;gap:var(--s-3);
  padding:var(--s-3);border:1px solid var(--line);border-radius:var(--radius-sm);
  background:var(--panel-2);text-decoration:none;color:inherit;
}
.eventrow:hover{border-color:var(--accent)}
.eventrow .thumb{
  width:52px;height:52px;border-radius:8px;object-fit:cover;flex:none;
  background:var(--line);display:block;
}
.eventrow .meta{flex:1;min-width:0}
.eventrow .meta b{display:block;font-weight:650;color:var(--ink)}
.eventrow .meta span{font-size:var(--t-xs);color:var(--dim)}

/* ============================================================ responsive */
@media (max-width:420px){
  :root{--t-3xl:1.95rem}
  .wrap{padding:var(--s-4) var(--s-3) var(--s-6)}
  .card{padding:var(--s-4)}
  .grid{grid-template-columns:repeat(auto-fill,minmax(96px,1fr))}
  .stats{gap:var(--s-5)}
  .themepanel{right:auto;left:0}
}
@media (prefers-reduced-motion:reduce){
  *{transition:none!important;animation:none!important}
}

/* ============================================================ image loading */
/* Gallery tiles paint a shimmer while the thumbnail is on its way, then the
   photo fades in from a soft blur instead of popping. Scoped to the two gallery
   grids (#ggrid guest, #pgrid admin) so the upload progress tiles, which manage
   their own previews, are untouched. */
#ggrid .cell,#pgrid .cell{background:var(--panel-2)}
#ggrid .cell.loading,#pgrid .cell.loading{
  background:linear-gradient(100deg,var(--panel-2) 30%,var(--bg-tint) 50%,var(--panel-2) 70%);
  background-size:220% 100%;
  animation:rpshine 1.25s linear infinite;
}
@keyframes rpshine{from{background-position:180% 0}to{background-position:-180% 0}}
/* The hidden state is applied only once JS has marked the tile (the .loading
   class), so a page where the script never runs still shows the photos instead
   of an empty grid. reveal() removes .loading and adds .ready on load. */
#ggrid .cell.loading img,#pgrid .cell.loading img{
  opacity:0;filter:blur(8px);
  transition:opacity .5s var(--lux-ease,ease),filter .6s var(--lux-ease,ease);
}
#ggrid .cell img.ready,#pgrid .cell img.ready{opacity:1;filter:blur(0)}
.hero img{opacity:0;transition:opacity .6s var(--lux-ease,ease)}
.hero img.ready{opacity:1}

/* ============================================================ upload ring */
/* One ring per file in the upload grid, filling with progress and resolving
   into a checkmark. The batch-level linear bar stays where it is. */
.ring{position:absolute;inset:0;display:grid;place-items:center;z-index:1;
      pointer-events:none}
.ring svg{
  width:46px;height:46px;transform:rotate(-90deg);overflow:visible;
  filter:drop-shadow(0 1px 3px rgba(0,0,0,.35));
}
.ring circle{fill:none;stroke-width:3;stroke-linecap:round}
.ring .track{stroke:rgba(255,255,255,.4)}
.ring .prog{stroke:var(--accent);transition:stroke-dashoffset .2s linear}
.ring .rpct{position:absolute;font-size:.6rem;font-weight:700;color:#fff}
.grid .cell .check{
  position:absolute;inset:0;display:none;place-items:center;z-index:2;
  font-size:1.7rem;color:#fff;pointer-events:none;
  text-shadow:0 1px 8px rgba(0,0,0,.55);
}
.grid .cell.ok .ring{display:none}
.grid .cell.ok .check{display:grid}

/* ============================================================ lightbox */
.lb{
  position:fixed;inset:0;z-index:200;display:none;
  align-items:center;justify-content:center;
  background:rgba(18,14,12,.94);padding:var(--s-4);
  -webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);
}
.lb.open{display:flex}
.lb-stage{
  position:relative;display:flex;align-items:center;justify-content:center;
  max-width:100%;max-height:100%;touch-action:pan-y;
}
.lb-stage img,.lb-stage video{
  max-width:100%;max-height:calc(100vh - 132px);
  border-radius:12px;display:block;background:#0e0c0b;
  box-shadow:0 24px 70px rgba(0,0,0,.6);
}
.lb-close{
  position:absolute;top:14px;right:14px;z-index:2;
  width:44px;height:44px;min-width:0;padding:0;margin:0;
  display:grid;place-items:center;border-radius:50%;
  background:rgba(255,255,255,.14);color:#fff;border:0;
  font-size:1.1rem;line-height:1;cursor:pointer;box-shadow:none;
}
.lb-close:hover{background:rgba(255,255,255,.26);transform:none}
.lb-nav{
  position:absolute;top:50%;transform:translateY(-50%);z-index:2;
  width:52px;height:52px;min-width:0;padding:0;margin:0;
  display:grid;place-items:center;border-radius:50%;
  background:rgba(255,255,255,.12);color:#fff;border:0;
  font-size:1.8rem;line-height:1;cursor:pointer;box-shadow:none;
}
.lb-nav:hover{background:rgba(255,255,255,.24);transform:translateY(-50%)}
.lb-prev{left:14px}
.lb-next{right:14px}
.lb-bar{
  position:absolute;left:0;right:0;bottom:16px;z-index:1;
  display:flex;flex-direction:column;align-items:center;gap:2px;
  pointer-events:none;text-align:center;padding:0 var(--s-4);
}
.lb-cap{color:#fff;font-size:var(--t-sm);font-weight:600;
        text-shadow:0 1px 8px rgba(0,0,0,.6)}
.lb-count{color:rgba(255,255,255,.7);font-size:var(--t-xs);
          font-variant-numeric:tabular-nums}
.lb-actions{
  position:absolute;top:14px;left:14px;z-index:2;
  display:flex;align-items:center;gap:var(--s-3);flex-wrap:wrap;
}
.lb-lock{
  color:rgba(255,255,255,.85);font-size:var(--t-xs);
  background:rgba(255,255,255,.12);padding:.28rem .7rem;
  border-radius:var(--radius-pill);
}
.lb-open,.lb-dl{
  color:#fff;font-size:var(--t-xs);font-weight:650;text-decoration:none;
  background:rgba(255,255,255,.14);padding:.34rem .8rem;
  border-radius:var(--radius-pill);
}
.lb-open:hover,.lb-dl:hover{background:rgba(255,255,255,.26);text-decoration:none}
@media (max-width:520px){
  .lb-nav{width:44px;height:44px;font-size:1.5rem}
  .lb-stage img,.lb-stage video{max-height:calc(100vh - 150px);border-radius:10px}
  .lb-actions{top:auto;bottom:64px;left:12px}
}
