/* ==========================================================
   .JPG CONVERTER — design tokens
========================================================== */
:root{
  --white:#FFFFFF;
  --ink:#1D1D1F;
  --surface:#F5F5F7;
  --line:#D2D2D7;
  --muted:#86868B;
  --radius-card:16px;
  --radius-pill:980px;
  --font: "PingFang TC", -apple-system, BlinkMacSystemFont, "Helvetica Neue", "Noto Sans TC", "Segoe UI", sans-serif;
  --font-mono: "SF Mono", ui-monospace, "Courier New", monospace;
  --ease: cubic-bezier(.22,.61,.36,1);
}

*{ box-sizing:border-box; }
html,body{ margin:0; padding:0; height:100%; overflow:hidden; }
body{
  font-family:var(--font);
  background:var(--white);
  color:var(--ink);
  -webkit-font-smoothing:antialiased;
}
h1,h2,h3,p{ margin:0; }
button{ font-family:inherit; }
::selection{ background:var(--ink); color:var(--white); }
:focus-visible{ outline:2px solid var(--ink); outline-offset:3px; }

/* Elements that already carry their own border get a border-color
   focus indicator instead of an extra outline ring, so focus never
   shows as two concentric frames. */
select:focus-visible,
input:focus-visible,
.btn-outline:focus-visible,
.btn-ghost:focus-visible,
.pos-grid button:focus-visible,
.custom-select-trigger:focus-visible,
.dropzone:focus-visible{
  outline:none;
  border-color:var(--ink);
}

.hidden{ display:none !important; }
.hidden-field{ display:none; }

/* ==========================================================
   HERO
========================================================== */
#hero{
  position:fixed;
  inset:0;
  background:var(--white);
  display:flex;
  align-items:center;
  justify-content:center;
  z-index:100;
  cursor:pointer;
  transition:opacity .6s var(--ease);
}
#hero.hero-exit{ opacity:0; pointer-events:none; }

.hero-inner{
  text-align:center;
  padding:0 24px;
}

/* Combo.svg viewBox is 1332.77×255.9 — mark + wordmark fused into one
   asset (same file the App's IntroView.swift uses). `--hero-logo-h` sets
   the on-screen height; the mark alone is a perfect square of that
   height (its paths only use the leading viewBox x 0...255.9), so a
   crop exactly that wide shows the mark with nothing of the wordmark
   bleeding in. `.hero-logo` starts at that square width, centered via
   `margin:0 auto`, then `.open` widens it to the full lockup width —
   because the box stays centered while it widens, the mark visibly
   slides left as a side effect, matching the App intro's reveal without
   needing two separate images or manual offset math. */
:root{ --hero-logo-h: clamp(36px, 8vw, 64px); }

.hero-logo{
  margin:0 auto;
  height:var(--hero-logo-h);
  width:var(--hero-logo-h);
  overflow:hidden;
  opacity:0;
  transition:opacity .3s ease-out, width .6s var(--ease);
}
.hero-logo.visible{ opacity:1; }
.hero-logo.open{ width:calc(var(--hero-logo-h) * 5.2082); }

.hero-logo-img{
  display:block;
  height:100%;
  width:auto;
}

.hero-credit{
  position:fixed;
  bottom:36px;
  left:0;
  right:0;
  text-align:center;
  font-size:clamp(11px, 1.6vw, 14px);
  letter-spacing:.15em;
  word-spacing:.2em;
  color:var(--muted);
  opacity:0;
  transition:opacity .6s var(--ease);
}
.hero-credit.show{ opacity:1; }

@media (prefers-reduced-motion: reduce){
  .hero-logo, .hero-credit{ transition:none; }
}

/* ==========================================================
   APP SHELL
========================================================== */
#app{
  opacity:0;
  transition:opacity .6s var(--ease);
  height:100vh;
  /* dvh tracks the *visible* viewport as mobile browsers show/hide their
     URL bar; 100vh alone leaves a gap or overflows on iOS Safari. */
  height:100dvh;
  display:flex;
  flex-direction:column;
  overflow:hidden;
}
#app.app-visible{ opacity:1; }

.app-header{
  border-bottom:1px solid var(--line);
  flex-shrink:0;
}
/* The header and .app-body must use the SAME padding model — padding applied
   to the centred max-width box itself, not to an outer full-bleed wrapper —
   or the logo/language pill sit up to 48px further out than the columns
   below them at wide viewports. */
.app-header-inner{
  display:flex;
  align-items:center;
  gap:clamp(20px, 4vw, 40px);
  max-width:1400px;
  margin:0 auto;
  padding:14px clamp(20px, 4vw, 48px);
  padding-left:max(clamp(20px, 4vw, 48px), env(safe-area-inset-left));
  padding-right:max(clamp(20px, 4vw, 48px), env(safe-area-inset-right));
}
.logo-slot{
  height:44px;
  border-radius:10px;
  flex-shrink:0;
  display:flex;
  align-items:center;
  cursor:pointer;
  transition:transform .15s var(--ease);
}
.logo-slot:hover{
  transform:scale(1.04);
}
.logo-slot:active{ transform:scale(.96); }
.logo-slot .logo-combo{ height:28px; width:auto; display:block; }
.feature-nav{
  display:flex;
  gap:clamp(8px, 2.5vw, 36px);
  font-size:clamp(9px, 3.2vw, 12px);
  letter-spacing:.1em;
  color:var(--muted);
  min-width:0;
  flex-shrink:1;
}
.feature-nav span{ white-space:nowrap; }

.header-spacer{ flex:1; }
.header-actions{
  display:flex;
  align-items:center;
  gap:10px;
  flex-shrink:0;
}

.menu-toggle{
  display:none;
  position:relative;
  z-index:230;
  flex-direction:column;
  justify-content:center;
  align-items:center;
  gap:4px;
  width:36px; height:36px;
  border:none;
  background:none;
  cursor:pointer;
  flex-shrink:0;
}
.menu-toggle span{
  display:block;
  width:20px; height:2px;
  background:var(--ink);
  border-radius:1px;
  transition:transform .2s var(--ease), opacity .2s var(--ease);
}
.menu-toggle.open span:nth-child(1){ transform:translateY(6px) rotate(45deg); }
.menu-toggle.open span:nth-child(2){ opacity:0; }
.menu-toggle.open span:nth-child(3){ transform:translateY(-6px) rotate(-45deg); }

.mobile-import{ display:none; flex-shrink:0; margin-top:18px; }

.drawer-backdrop{
  display:none;
  position:fixed; inset:0;
  background:rgba(20,20,20,.25);
  z-index:210;
  opacity:0;
  pointer-events:none;
  transition:opacity .3s var(--ease);
}
.drawer-backdrop.show{ opacity:1; pointer-events:auto; }

/* ==========================================================
   MAIN BODY LAYOUT
========================================================== */
.app-body{
  flex:1;
  min-height:0;
  max-width:1400px;
  width:100%;
  margin:0 auto;
  padding:20px clamp(20px, 4vw, 48px) 16px;
  padding-left:max(clamp(20px, 4vw, 48px), env(safe-area-inset-left));
  padding-right:max(clamp(20px, 4vw, 48px), env(safe-area-inset-right));
  display:grid;
  grid-template-columns:320px 1fr;
  gap:32px;
  align-items:stretch;
  overflow:hidden;
}
/* The page itself never scrolls (see html,body{overflow:hidden}) — but
   the settings sidebar can scroll internally when it needs to (e.g. the
   watermark options expanding, or a short window), it just isn't forced
   into a cramped, artificially-shrunk layout to avoid ever needing to.
   The photo grid works the same way for the same reason: an unbounded
   number of imported photos can never be guaranteed to fit on one screen,
   so `.stage` is a column where the toolbar/dropzone stay fixed-size and
   only `.grid` scrolls internally. */
.settings{
  height:100%;
  min-height:0;
  overflow-y:auto;
}
.stage{
  height:100%;
  min-height:0;
  display:flex;
  flex-direction:column;
  overflow:hidden;
}
@media (min-width:981px){
  .app-body .stage{ order:2; }
  .app-body .settings{ order:1; }
}
/* Between the drawer breakpoint and full desktop the sidebar is inline but
   space is tight, so it narrows rather than squeezing the thumbnail grid
   down to a single column. */
@media (min-width:981px) and (max-width:1180px){
  .app-body{ grid-template-columns:264px 1fr; gap:24px; }
}
@media (max-width:980px){
  .app-body{ grid-template-columns:1fr; }
  .stage{ order:1; }

  .menu-toggle{ display:flex; }
  .mobile-import{ display:block; }
  .settings .import-group{ display:none; }
  .drawer-backdrop{ display:block; }

  /* The settings panel becomes a left-hand slide-in drawer instead of an
     inline column, so the main mobile screen (toolbar, dropzone, import,
     photo grid) stays short and doesn't need the whole settings list
     rendered inline underneath it. Sliding in from the left keeps the same
     side as the desktop layout's settings column (see the order:1 rule
     above), instead of the right side.
     Uses the #settingsPanel id (not just .settings) so this reliably wins
     over the later, unconditional ".settings{ position:sticky }" desktop
     rule regardless of where each rule sits in the file — same specificity
     class selectors would otherwise let source order decide, which broke
     this on mobile. */
  #settingsPanel{
    order:2;
    position:fixed;
    top:0; left:0; bottom:0;
    width:min(86vw, 340px);
    max-height:none;
    background:var(--white);
    border-right:1px solid var(--line);
    padding:20px;
    padding-left:max(20px, env(safe-area-inset-left));
    padding-bottom:max(32px, env(safe-area-inset-bottom));
    overflow-y:auto;
    /* keep flicks inside the drawer from scrolling the page behind it */
    overscroll-behavior:contain;
    -webkit-overflow-scrolling:touch;
    transform:translateX(-100%);
    transition:transform .3s var(--ease);
    z-index:220;
  }
  @media (prefers-reduced-motion: reduce){
    #settingsPanel{ transition:none; }
  }
  #settingsPanel.open{ transform:translateX(0); }
}

/* ==========================================================
   STAGE (thumbnail area)
========================================================== */
.stage-toolbar{
  flex-shrink:0;
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:16px;
  flex-wrap:wrap;
  margin-bottom:18px;
}
.stats{
  font-size:13px;
  color:var(--muted);
}
.stage-actions{ display:flex; gap:10px; flex-wrap:wrap; }

.dropzone{
  border:1px dashed var(--line);
  border-radius:var(--radius-card);
  padding:32px 20px;
  min-height:clamp(240px, 38vh, 440px);
  display:flex;
  flex-direction:column;
  align-items:center;
  justify-content:center;
  text-align:center;
  background:var(--surface);
  cursor:pointer;
  transition:border-color .15s, background .15s;
}
.dropzone-icon{
  width:44px;
  height:44px;
  display:block;
  margin-bottom:14px;
}
.dropzone:hover, .dropzone:focus-visible{ border-color:var(--ink); }
.dropzone.dragging{
  border-color:var(--ink);
  background:var(--surface);
  animation:dropzone-breathe 1.2s ease-in-out infinite;
}
.dropzone.importing{
  border-color:var(--ink);
  background:var(--surface);
  cursor:wait;
  pointer-events:none;
  animation:dropzone-breathe 1.2s ease-in-out infinite;
}
@keyframes dropzone-breathe{
  0%, 100%{ box-shadow:0 0 0 0 rgba(29,29,31,0); }
  50%{ box-shadow:0 0 0 6px rgba(29,29,31,.06); }
}
@media (prefers-reduced-motion: reduce){
  .dropzone.dragging, .dropzone.importing{ animation:none; }
}
.dropzone-cta{ font-size:17px; font-weight:600; margin-bottom:6px; }
.dropzone-sub{ font-size:13px; color:var(--muted); }

.grid{
  margin-top:18px;
  display:grid;
  grid-template-columns:repeat(auto-fill, minmax(200px,1fr));
  gap:18px;
  /* The one deliberate exception to "nothing scrolls but the page itself" —
     an unbounded number of photos can't be guaranteed to fit on one screen,
     so this scrolls while the toolbar/dropzone above it stay put. */
  flex:1;
  min-height:0;
  overflow-y:auto;
}

.card{
  background:var(--white);
  border:1px solid var(--line);
  border-radius:var(--radius-card);
  overflow:hidden;
  transition:transform .2s var(--ease), box-shadow .2s var(--ease);
  animation:card-in .4s var(--ease) backwards;
}
@keyframes card-in{
  from{ opacity:0; transform:translateY(10px); }
  to{ opacity:1; transform:translateY(0); }
}
@media (prefers-reduced-motion: reduce){
  .card{ animation:none; }
}
.card:hover{
  transform:translateY(-3px);
  box-shadow:0 14px 30px rgba(0,0,0,.08);
}
.card .thumb{
  position:relative;
  aspect-ratio:4/3;
  background:var(--surface);
  overflow:hidden;
}
.card .thumb img{
  width:100%; height:100%; object-fit:cover; display:block;
  transition:transform .35s var(--ease);
  cursor:zoom-in;
}
.card:hover .thumb img{ transform:scale(1.06); }
.card .thumb .remove-btn{
  position:absolute; top:8px; right:8px;
  width:24px; height:24px; border-radius:50%;
  background:rgba(255,255,255,.9);
  border:1px solid var(--line);
  color:var(--ink);
  font-size:13px; line-height:1;
}
.card .select-check{
  position:absolute; top:8px; left:8px; z-index:2;
  width:24px; height:24px; border-radius:7px;
  background:rgba(255,255,255,.9);
  border:1px solid var(--line);
  display:flex; align-items:center; justify-content:center;
  cursor:pointer;
}
.card .select-check input{ width:15px; height:15px; margin:0; accent-color:var(--ink); }
.card .status-tag{
  position:absolute; bottom:8px; left:8px;
  font-size:10px; letter-spacing:.06em;
  padding:3px 8px; border-radius:var(--radius-pill);
  background:rgba(255,255,255,.92);
  border:1px solid var(--line);
  color:var(--muted);
}
.card .status-tag.done{ color:#1D1D1F; border-color:var(--ink); }
.card.selected{ box-shadow:0 0 0 2px var(--ink); }
.card.dragging-card{ opacity:.4; }
.card.drop-target{ outline:2px dashed var(--ink); outline-offset:-3px; }
.card{ cursor:grab; touch-action:pan-y; }
.card:active{ cursor:grabbing; }
.card .progress-track{ height:2px; background:var(--surface); overflow:hidden; }
.card .progress-fill{
  height:100%; width:0%; background:var(--ink); transition:width .25s;
  position:relative;
}
.card .progress-fill.active::after{
  content:"";
  position:absolute; top:0; left:0; right:0; bottom:0;
  background:linear-gradient(90deg, transparent, rgba(255,255,255,.55), transparent);
  animation:progress-shimmer 1.1s linear infinite;
}
@keyframes progress-shimmer{
  from{ transform:translateX(-100%); }
  to{ transform:translateX(100%); }
}
.card .meta{ padding:12px 14px 14px; }
.card .fname{ font-size:12.5px; font-weight:500; word-break:break-all; margin-bottom:3px; }
.card .dims{ font-size:11.5px; color:var(--muted); }
.card .arrow{ font-size:11.5px; color:var(--ink); margin-top:2px; }
.card .exif-toggle{
  margin-top:8px; background:none; border:none; padding:0;
  font-size:11px; color:var(--muted); text-decoration:underline;
  text-underline-offset:2px;
}
.card .exif-panel{
  display:none; margin-top:8px; padding:10px;
  background:var(--surface); border-radius:8px;
  font-size:11px; font-family:var(--font-mono);
}
.card .exif-panel.open{ display:block; }
.card .exif-panel div{ display:flex; flex-wrap:wrap; justify-content:space-between; gap:2px 8px; padding:2px 0; color:var(--ink); }
.card .exif-panel .k{ color:var(--muted); }
.card .dl-row{ margin-top:10px; }

/* ==========================================================
   SETTINGS PANEL
========================================================== */
.settings{
  padding-bottom:24px;
}
.setting-group{
  padding:16px 0;
  border-bottom:1px solid var(--line);
}
.setting-group:first-child{ padding-top:0; }
.setting-group:last-child{ border-bottom:none; }
.setting-group h2{
  font-size:12px;
  letter-spacing:.1em;
  color:var(--muted);
  margin-bottom:10px;
  font-weight:600;
}
.field-label{
  display:block;
  font-size:12.5px;
  color:var(--ink);
  margin:13px 0 7px;
}
.field-label:first-child{ margin-top:0; }
.value-badge{ color:var(--muted); font-family:var(--font-mono); }

#qualityField{ margin-top:8px; }

select, input[type="text"], input[type="number"]{
  width:100%;
  padding:9px 12px;
  border:1px solid var(--line);
  border-radius:10px;
  background:var(--white);
  color:var(--ink);
  font-size:14px;
}
select:focus, input:focus{ border-color:var(--ink); }
input[type="range"]{ width:100%; accent-color:var(--ink); }
input[type="file"]{ font-size:12.5px; }

/* native <select> stays in the DOM to hold state, but is visually replaced
   by the custom dropdown below it */
select.native-select-hidden{
  position:absolute;
  width:1px; height:1px;
  opacity:0; overflow:hidden;
  pointer-events:none;
}

.custom-select{
  position:relative;
  width:100%;
}
.custom-select-trigger{
  width:100%;
  display:flex; align-items:center; justify-content:space-between;
  gap:8px;
  padding:9px 12px;
  border:1px solid var(--line);
  border-radius:10px;
  background:var(--white);
  color:var(--ink);
  font-size:14px;
  text-align:left;
  cursor:pointer;
  transition:border-color .15s;
}
.custom-select-trigger:hover{ border-color:#b8b8bd; }
.custom-select.open .custom-select-trigger{ border-color:var(--ink); }
.custom-select-chevron{
  width:9px; height:9px;
  flex-shrink:0;
  border-right:1.5px solid var(--muted);
  border-bottom:1.5px solid var(--muted);
  transform:rotate(45deg);
  transition:transform .15s;
  margin-top:-3px;
}
.custom-select.open .custom-select-chevron{ transform:rotate(-135deg); margin-top:3px; }

.custom-select-list{
  list-style:none;
  margin:6px 0 0;
  padding:6px;
  position:absolute;
  top:100%; left:0; right:0;
  background:var(--white);
  border:1px solid var(--line);
  border-radius:12px;
  box-shadow:0 12px 28px rgba(0,0,0,.10);
  z-index:20;
  max-height:240px;
  overflow-y:auto;
  display:none;
}
.custom-select.open .custom-select-list{ display:block; }
.custom-select-option{
  padding:9px 10px;
  border-radius:7px;
  font-size:13.5px;
  color:var(--ink);
  cursor:pointer;
}
.custom-select-option:hover{ background:var(--surface); }
.custom-select-option.highlighted{ background:var(--surface); }
.custom-select-option.selected{ font-weight:600; }
.custom-select-option.selected::after{ content:"✓"; float:right; color:var(--ink); }

/* Links to download.html. Visually matches the language pill next to it
   (same border/radius/padding/type scale) so it doesn't read as a
   mismatched afterthought. It's an <a>, so it needs the inline-flex and
   underline resets a <button> would get for free. */
.download-mac-btn{
  display:inline-flex;
  align-items:center;
  text-decoration:none;
  border:1px solid var(--ink);
  border-radius:var(--radius-pill);
  padding:8px 14px;
  font-size:12px;
  font-weight:600;
  letter-spacing:.02em;
  background:var(--white);
  color:var(--ink);
  cursor:pointer;
  white-space:nowrap;
  flex-shrink:0;
  transition:background .15s, transform .1s var(--ease);
}
.download-mac-btn:hover{ background:var(--surface); }
.download-mac-btn:active{ transform:scale(.96); }

/* language pill dropdown reuses the same mechanism with a compact look */
.lang-custom-select .custom-select-trigger{
  border:1px solid var(--ink);
  border-radius:var(--radius-pill);
  padding:8px 14px;
  font-size:12px;
  font-weight:600;
  letter-spacing:.06em;
  width:auto;
}
.lang-custom-select .custom-select-trigger:hover{ background:var(--surface); }
.lang-custom-select .custom-select-list{
  left:auto; right:0;
  min-width:120px;
}
.lang-custom-select .custom-select-chevron{
  border-color:var(--ink);
  width:6px; height:6px;
}

.row2{ display:grid; grid-template-columns:1fr 1fr; gap:10px; }

.switch-row{
  display:flex; align-items:center; gap:10px;
  font-size:14px;
}
.switch-row input{ width:16px; height:16px; accent-color:var(--ink); }
.hint-text{ font-size:11.5px; color:var(--muted); margin-top:6px; }

.thumb .wm-preview,
.lightbox-media .wm-preview{
  position:absolute; inset:0; pointer-events:none; overflow:hidden;
}
.wm-preview .wm-text{
  position:absolute;
  white-space:nowrap;
  font-weight:600;
  text-shadow:0 1px 3px rgba(0,0,0,.4);
}
.wm-preview .wm-image{
  position:absolute;
  max-width:80%;
}

.pos-grid{
  display:grid; grid-template-columns:repeat(3,1fr); gap:5px;
  padding:6px; border:1px solid var(--line); border-radius:10px;
}
.pos-grid button{
  aspect-ratio:1; border:1px solid var(--line); border-radius:6px;
  background:var(--white); position:relative;
}
.pos-grid button::after{
  content:""; position:absolute; top:50%; left:50%;
  width:5px; height:5px; border-radius:50%;
  background:var(--muted); transform:translate(-50%,-50%);
}
.pos-grid button.active{ border-color:var(--ink); background:var(--surface); }
.pos-grid button.active::after{ background:var(--ink); }

/* ==========================================================
   BUTTONS
========================================================== */
.btn{
  font-size:14px;
  font-weight:600;
  border-radius:var(--radius-pill);
  padding:11px 20px;
  border:1px solid transparent;
  cursor:pointer;
  transition:background .15s, border-color .15s, color .15s, opacity .15s, transform .1s var(--ease);
}
.btn:active:not(:disabled){ transform:scale(.96); }
.btn.full{ width:100%; }
.btn-primary{ background:var(--ink); color:var(--white); }
.btn-primary:hover{ background:#000; }
.btn-outline{ background:var(--white); border-color:var(--ink); color:var(--ink); }
.btn-outline:hover{ background:var(--surface); }
.btn-ghost{ background:var(--surface); border-color:var(--surface); color:var(--ink); }
.btn-ghost:hover{ background:var(--line); }
.btn:disabled{ opacity:.4; cursor:not-allowed; }

/* ==========================================================
   FOOTER
========================================================== */
.app-footer{
  flex-shrink:0;
  padding:8px 20px 14px;
  padding-bottom:calc(14px + env(safe-area-inset-bottom));
  text-align:center;
}
.app-footer p{
  /* Fluid rather than a fixed size + breakpoint overrides — scales
     continuously with viewport width so the line stays on one row from
     the narrowest phones up, instead of only being tuned for the couple
     of widths someone happened to test at. */
  font-size:clamp(9px, 2.6vw, 12px);
  color:var(--muted);
  letter-spacing:.02em;
  white-space:nowrap;
  margin:2px 0;
}
.app-footer a{ color:var(--muted); text-decoration:none; }
.app-footer a:hover{ text-decoration:underline; }

/* ==========================================================
   TOAST
========================================================== */
.toast{
  position:fixed;
  left:50%;
  bottom:28px;
  transform:translate(-50%, 12px);
  background:var(--ink);
  color:var(--white);
  font-size:13px;
  font-weight:500;
  padding:12px 20px;
  border-radius:var(--radius-pill);
  box-shadow:0 12px 28px rgba(0,0,0,.18);
  opacity:0;
  pointer-events:none;
  z-index:200;
  max-width:min(90vw, 420px);
  text-align:center;
  transition:opacity .25s var(--ease), transform .25s var(--ease);
}
.toast.show{
  opacity:1;
  transform:translate(-50%, 0);
}
@media (prefers-reduced-motion: reduce){
  .toast{ transition:opacity .15s linear; transform:translate(-50%, 0); }
}

/* ==========================================================
   LIGHTBOX (enlarged thumbnail preview)
========================================================== */
.lightbox{
  position:fixed;
  inset:0;
  background:rgba(20,20,20,.92);
  display:flex;
  align-items:center;
  justify-content:center;
  z-index:250;
  /* Top padding clears the close button, which is pinned to the overlay's
     top-right; without it the button sits on top of the image on phones. */
  padding:clamp(64px, 9vw, 80px) clamp(14px, 4vw, 40px) clamp(24px, 6vw, 40px);
  opacity:0;
  pointer-events:none;
  transition:opacity .2s var(--ease);
}
.lightbox.show{ opacity:1; pointer-events:auto; }
.lightbox-media{
  position:relative;
  display:inline-flex;
  max-width:100%;
  max-height:100%;
}
.lightbox-media img{
  display:block;
  max-width:100%;
  max-height:100%;
  border-radius:8px;
  box-shadow:0 20px 60px rgba(0,0,0,.4);
}
.lightbox-close{
  position:absolute;
  top:max(16px, env(safe-area-inset-top));
  right:max(16px, env(safe-area-inset-right));
  width:44px; height:44px;
  border-radius:50%;
  border:none;
  background:rgba(255,255,255,.12);
  color:var(--white);
  font-size:22px;
  line-height:1;
  cursor:pointer;
  transition:background .15s;
}
.lightbox-close:hover{ background:rgba(255,255,255,.22); }
@media (prefers-reduced-motion: reduce){
  .lightbox{ transition:opacity .1s linear; }
}

/* ==========================================================
   RESPONSIVE
========================================================== */
/* ---- tablets / small laptops (drawer is already active here) ---- */
@media (max-width:980px){
  .app-body{ padding-top:16px; }
  .grid{ grid-template-columns:repeat(auto-fill, minmax(170px,1fr)); }
  /* Decorative only (aria-hidden) — the mobile drawer already takes over
     navigation at this width, and there isn't room left for it without
     the header wrapping onto a second row (seen breaking around 517px). */
  .feature-nav{ display:none; }
}

/* ---- phones ---- */
@media (max-width:640px){
  .app-header-inner{ gap:10px; flex-wrap:wrap; row-gap:10px; }
  .header-spacer{ display:none; }
  /* No .header-spacer to push these right once it's hidden, so pull the
     language switch (and everything after it — the menu button) over to
     the right edge as a group; the row's own `gap` then keeps the two
     flush against each other instead of spread apart. */
  /* `.download-mac-btn` and `.lang-switch` are grouped under
     `.header-actions` precisely so this auto-margin (and any wrapping)
     moves them together instead of letting them drift apart/reorder
     when the row wraps. */
  .header-actions{ margin-left:auto; }
  .grid{ grid-template-columns:repeat(auto-fill, minmax(150px,1fr)); gap:12px; }
  .dropzone{ padding:40px 16px; }

  /* Stack the toolbar so the count and the two actions each get a full row
     instead of the buttons wrapping into a ragged second line. */
  .stage-toolbar{
    flex-direction:column;
    align-items:stretch;
    gap:12px;
  }
  .stage-actions{ display:grid; grid-template-columns:1fr 1fr; gap:10px; }
  .stage-actions .btn{ width:100%; }

  /* 44px is the minimum comfortable touch target on iOS/Android. */
  .btn{ min-height:44px; padding:12px 18px; }
  .card .remove-btn,
  .card .select-check{ width:30px; height:30px; }

  .hero-credit{ bottom:max(24px, env(safe-area-inset-bottom)); }
}

/* ---- narrow phones (iPhone SE and similar) ---- */
@media (max-width:420px){
  .grid{ grid-template-columns:repeat(auto-fill, minmax(132px,1fr)); gap:10px; }
  /* Stay 2-up rather than collapsing to 1 column — shrink the buttons
     themselves instead so "開始批次處理" / "下載全部 (.zip)" both still
     fit side by side at this width. */
  .stage-actions .btn{ padding:11px 8px; font-size:12.5px; }
  /* Two number fields side by side get unusably narrow below this width. */
  .row2{ grid-template-columns:1fr; }
  .dropzone{ padding:32px 12px; }
  .dropzone-icon{ width:36px; height:36px; margin-bottom:10px; }
  .dropzone-cta{ font-size:15px; }
  .dropzone-sub{ font-size:12px; }
  .card .meta{ padding:10px 11px 12px; }
}

/* ---- short viewports (landscape phones) ---- */
@media (max-height:520px) and (orientation:landscape){
  /* The hero's fixed credit line collides with the logo when there's
     almost no vertical room. */
  .hero-credit{ display:none; }
  .lightbox{ padding:56px 16px 16px; }
}

/* ---- touch devices ----
   Keyed off the pointer type rather than viewport width: a large tablet
   still needs finger-sized targets, and a narrow desktop window does not. */
@media (pointer:coarse){
  .switch-row{ min-height:44px; }
  .switch-row input{ width:20px; height:20px; }
  .card .select-check input{ width:18px; height:18px; }
  .pos-grid button{ min-height:38px; }
  .custom-select-option{ padding:12px 10px; }
  .custom-select-trigger{ padding:12px; }
  select, input[type="text"], input[type="number"]{ padding:12px; }
  .card .exif-toggle{ padding:6px 0; }
  /* hover transforms are noise on touch and leave elements stuck scaled up */
  .card:hover{ transform:none; box-shadow:none; }
  .card:hover .thumb img{ transform:none; }
  .logo-slot:hover{ transform:none; }
}

