/* Drop — dark only, on purpose. This is a utility that gets opened on a phone
   at odd hours; a white flash is the wrong first impression and there is no
   brand reason to offer a light variant.

   Background is the iOS system dark gradient from the handbook: a lighter
   near-black at the top falling to true black. The stops are pinned to fixed
   pixel distances rather than percentages, because across a full viewport the
   per-pixel change is a couple of RGB units and reads as flat. */

:root {
  --bg: #000;
  --card: rgba(255, 255, 255, 0.055);
  --card-hover: rgba(255, 255, 255, 0.085);
  --line: rgba(255, 255, 255, 0.11);
  --text: #f2f2f7;
  --muted: #98989f;
  /* Metallic gold, sampled off the icon art: highlight, body, shadow. Used as
     a three-stop gradient on anything that should read as metal rather than as
     flat paint, which is the whole difference between gold and yellow. */
  --gold-hi: #f0dcb0;
  --gold: #d4b071;
  --gold-lo: #a8823f;
  --accent: var(--gold);
  --on-accent: #14100a;
  --danger: #ff453a;
  --radius: 16px;
  --dock-h: calc(64px + env(safe-area-inset-bottom, 0px));
}

* { box-sizing: border-box; }

/* The `hidden` attribute is enforced by a UA rule with almost no specificity,
   so ANY author rule that sets display on the same element silently defeats it.
   This bit hard: .dropzone and .center both set display:grid, so the drag
   overlay rendered on top of the whole app the instant the app unhid, and the
   boot and gate screens stacked underneath it. Nothing in the markup looked
   wrong and every asset returned 200. Only a screenshot showed it.
   Keep this rule above everything that follows. */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
}

body {
  background: linear-gradient(180deg, #2c2c2e 0%, #1c1c1e 220px, #000 480px, #000 100%);
  background-attachment: fixed;
  background-color: var(--bg);
  color: var(--text);
  font: 16px/1.45 -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, system-ui, sans-serif;
  -webkit-font-smoothing: antialiased;
  overscroll-behavior-y: none;
}

/* A soft accent bloom behind the top of the page. Purely atmospheric, and cheap
   enough that it costs nothing on a phone. */
.glow {
  position: fixed;
  top: -180px; left: 50%;
  width: 520px; height: 380px;
  transform: translateX(-50%);
  background: radial-gradient(closest-side, rgba(212, 176, 113, 0.13), transparent 70%);
  pointer-events: none;
  z-index: 0;
}

.screen { position: relative; z-index: 1; }
.center {
  min-height: 100dvh;
  display: grid;
  place-items: center;
  padding: 24px;
}

.muted { color: var(--muted); }
.error { color: var(--danger); font-size: 14px; margin: 10px 0 0; }

/* ---------- boot ---------- */
.spinner {
  width: 26px; height: 26px;
  border: 2px solid rgba(255,255,255,0.18);
  border-top-color: var(--text);
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ---------- gate ---------- */
.gate-card {
  width: min(360px, 100%);
  text-align: center;
}
.gate-card h1 { font-size: 30px; margin: 18px 0 6px; letter-spacing: -0.02em; }
.gate-card p { margin: 0 0 20px; font-size: 15px; }
.mark {
  width: 62px; height: 62px; margin: 0 auto;
  border-radius: 15px;
  background: linear-gradient(180deg, #313131, #141414);
  border: 1px solid var(--line);
  position: relative;
}
.mark::after {
  content: "";
  position: absolute; inset: 0;
  background: no-repeat center / 26px 26px
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23d4b071' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M12 3v13M7 11l5 5 5-5M5 21h14'/></svg>");
}
/* ---------- keypad ----------
   Ported from semester's sign-in, which Adam already uses on his phone, so the
   two apps ask for a PIN the same way. Two details there are load-bearing and
   easy to lose: the columns are a fixed px width rather than 1fr (stretched to
   the card the keys come out wider than tall, and a 50% radius then draws
   ovals), and there is no input element anywhere on this screen (focusing one
   raises the OS keyboard straight over the keypad). */

.pindots {
  display: flex;
  justify-content: center;
  gap: 16px;
  margin: 0 0 26px;
}
.pindot {
  width: 13px; height: 13px;
  border-radius: 50%;
  border: 1.5px solid var(--line);
  transition: background 0.14s ease, border-color 0.14s ease, transform 0.14s ease;
}
.pindot.on {
  background: var(--gold);
  border-color: var(--gold);
  transform: scale(1.12);
}

.keypad {
  display: grid;
  grid-template-columns: repeat(3, 72px);
  justify-content: center;
  gap: 14px;
}
.keypad button {
  width: 72px;
  aspect-ratio: 1; /* height follows width, so these can only be circles */
  border-radius: 50%;
  font-size: 24px;
  font-weight: 480;
  color: var(--text);
  background: var(--card);
  border: 1px solid var(--line);
  transition: transform 0.09s ease, background 0.14s ease;
}
.keypad button:active { transform: scale(0.93); background: var(--card-hover); }
.keypad button:disabled { opacity: 0.45; }
.keypad .keypad-text {
  font-size: 13.5px;
  font-weight: 560;
  color: var(--muted);
  background: none;
  border-color: transparent;
}

.gate-msg {
  min-height: 20px;
  margin: 18px 0 0;
  font-size: 13.5px;
  color: var(--muted);
}
.gate-msg.err { color: var(--danger); }

/* The dots shake rather than the whole card: the card holds the mark and the
   title, and shaking those reads as the app failing instead of the PIN. */
@keyframes shake {
  10%, 90% { transform: translateX(-2px); }
  20%, 80% { transform: translateX(4px); }
  30%, 50%, 70% { transform: translateX(-7px); }
  40%, 60% { transform: translateX(7px); }
}
.pindots.wrong { animation: shake 0.45s ease both; }

input[type=password], input[type=text] {
  width: 100%;
  padding: 14px 16px;
  font-size: 16px; /* 16px or iOS zooms the page on focus */
  color: var(--text);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 12px;
  outline: none;
}
input:focus { border-color: var(--accent); }

/* ---------- buttons ---------- */
button, .primary {
  font: inherit;
  cursor: pointer;
  border: none;
  border-radius: 12px;
}
.primary {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-height: 50px;
  padding: 0 22px;
  background: linear-gradient(180deg, var(--gold-hi) 0%, var(--gold) 46%, var(--gold-lo) 100%);
  color: var(--on-accent);
  font-weight: 650;
  text-decoration: none;
  transition: transform 0.12s ease, filter 0.12s ease;
}
.primary:active { transform: scale(0.975); filter: brightness(0.92); }
.primary svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; }

.icon-btn {
  width: 40px; height: 40px;
  display: grid; place-items: center;
  background: var(--card);
  border: 1px solid var(--line);
  color: var(--text);
  border-radius: 11px;
}
.icon-btn:active { background: var(--card-hover); }
.icon-btn svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.icon-btn.spinning svg { animation: spin 0.7s linear infinite; }

/* ---------- bar ---------- */
.bar {
  position: sticky; top: 0;
  z-index: 5;
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px;
  padding: calc(14px + env(safe-area-inset-top, 0px)) 18px 14px;
  backdrop-filter: saturate(1.4) blur(14px);
  -webkit-backdrop-filter: saturate(1.4) blur(14px);
  background: rgba(0, 0, 0, 0.5);
  border-bottom: 1px solid var(--line);
}
.bar h1 { font-size: 20px; margin: 0; letter-spacing: -0.01em; }
.bar-actions { display: flex; align-items: center; gap: 10px; }

.pill {
  font-size: 12px; font-weight: 600;
  padding: 5px 10px;
  border-radius: 999px;
}
.pill.offline { background: rgba(255, 159, 10, 0.16); color: #ff9f0a; }

/* ---------- list ---------- */
.list {
  padding: 14px 14px calc(var(--dock-h) + 22px);
  display: flex; flex-direction: column; gap: 10px;
  max-width: 720px; margin: 0 auto;
}

.row {
  display: flex; align-items: center; gap: 13px;
  padding: 11px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  animation: rise 0.22s ease both;
}
@keyframes rise { from { opacity: 0; transform: translateY(7px); } }
.row.going { opacity: 0.4; }

.thumb {
  width: 50px; height: 50px;
  flex: none;
  border-radius: 11px;
  background: rgba(255,255,255,0.06) no-repeat center / cover;
  display: grid; place-items: center;
  overflow: hidden;
}
.thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.thumb .ext {
  font-size: 11px; font-weight: 700; letter-spacing: 0.04em;
  color: var(--muted);
  text-transform: uppercase;
}

.meta { min-width: 0; flex: 1; display: block; }
.name, .sub { display: block; }
/* Wrap rather than truncate. An ellipsis on a filename hides the one part that
   tells them apart: "Adam Mirmina C..." and "Adam Mirmina R..." are the same
   string until the character that got cut. Two lines is cheap; guessing is not. */
.name {
  font-size: 15px; font-weight: 500;
  overflow-wrap: anywhere;
  line-height: 1.3;
}
.sub {
  font-size: 13px; color: var(--muted); margin-top: 2px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

.row-actions { display: flex; gap: 6px; flex: none; }

/* Progress ring drawn straight onto the thumbnail while an upload is in
   flight, so the file appears in the list the instant it is chosen rather than
   after the round trip. */
.row.pending .thumb { position: relative; }
.row.pending .thumb::after {
  content: "";
  position: absolute; inset: 0;
  background: conic-gradient(var(--accent) calc(var(--p, 0) * 1%), rgba(0,0,0,0.55) 0);
  mask: radial-gradient(circle, transparent 58%, #000 60%);
  -webkit-mask: radial-gradient(circle, transparent 58%, #000 60%);
}
.row.failed { border-color: rgba(255, 69, 58, 0.5); }

/* Delete asks first, inline. window.confirm is banned: it is an unstyled OS
   dialog that says "share.adammirmina.com says", blocks the thread, and cannot
   be themed. */
.confirm {
  display: flex; gap: 6px; align-items: center;
}
.confirm button {
  min-height: 40px; padding: 0 12px;
  font-size: 14px; font-weight: 600;
  background: var(--card);
  color: var(--text);
  border: 1px solid var(--line);
}
.confirm .yes { background: var(--danger); color: #fff; border-color: transparent; }

/* ---------- empty ---------- */
.empty {
  max-width: 380px;
  margin: 12vh auto 0;
  padding: 0 28px calc(var(--dock-h) + 20px);
  text-align: center;
}
.empty-mark {
  width: 56px; height: 56px; margin: 0 auto 16px;
  border-radius: 14px;
  border: 1px dashed var(--line);
  background: no-repeat center / 24px 24px
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2398989f' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M12 3v13M7 11l5 5 5-5M5 21h14'/></svg>");
}
.empty-title { font-size: 17px; font-weight: 600; margin: 0 0 6px; }
.empty p { font-size: 14.5px; margin: 0; }

/* ---------- dock ---------- */
.dock {
  position: fixed; left: 0; right: 0; bottom: 0;
  z-index: 6;
  padding: 10px 16px calc(10px + env(safe-area-inset-bottom, 0px));
  display: flex; justify-content: center;
  background: linear-gradient(180deg, transparent, rgba(0,0,0,0.82) 42%);
}
.dock-btn { width: min(420px, 100%); }

/* ---------- drag ---------- */
.dropzone {
  position: fixed; inset: 0;
  z-index: 20;
  padding: 18px;
  background: rgba(0, 0, 0, 0.72);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  display: grid; place-items: center;
}
.dropzone-inner {
  width: 100%; height: 100%;
  display: grid; place-items: center;
  border: 2px dashed var(--accent);
  border-radius: 22px;
  font-size: 18px; font-weight: 600;
}

/* ---------- viewer ----------
   Full-screen, but explicitly NOT a navigation. The close control is top-left,
   which is where a back affordance lives on both platforms, and it is a 40px
   target rather than a small glyph. Backdrop tap, Esc and swipe-down all close
   it too: in a standalone PWA the user has no browser chrome to fall back on,
   so one way out is not enough. */
.viewer {
  position: fixed; inset: 0;
  z-index: 40;
  display: flex; flex-direction: column;
  background: rgba(0, 0, 0, 0.94);
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
  animation: fade 0.16s ease both;
}
@keyframes fade { from { opacity: 0; } }

.viewer-bar {
  display: flex; align-items: center; gap: 12px;
  padding: calc(12px + env(safe-area-inset-top, 0px)) 14px 12px;
  border-bottom: 1px solid var(--line);
  flex: none;
}
.viewer-title {
  flex: 1; min-width: 0;
  font-size: 15px; font-weight: 500;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

.viewer-body {
  flex: 1; min-height: 0;
  display: grid; place-items: center;
  padding: 14px;
  overflow: auto;
  -webkit-overflow-scrolling: touch;
}
.viewer-body img {
  max-width: 100%; max-height: 100%;
  object-fit: contain;
  border-radius: 10px;
  display: block;
}
.viewer-body pre {
  margin: 0; align-self: start; justify-self: stretch;
  white-space: pre-wrap; word-break: break-word;
  font: 13px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--text);
}
.viewer-pdf {
  width: 100%;
  height: 100%;
  min-height: 60vh;
  border: 0;
  border-radius: 10px;
  background: #fff;
  justify-self: stretch;
  align-self: stretch;
}

.viewer-body .none {
  text-align: center; color: var(--muted); font-size: 15px; max-width: 280px;
}

/* The whole row opens the viewer, so it needs to read as pressable. */
.open {
  display: flex; align-items: center; gap: 13px;
  flex: 1; min-width: 0;
  background: none; border: none; padding: 0;
  color: inherit; text-align: left;
  font: inherit;
}
.open:active { opacity: 0.65; }

/* ---------- toast ---------- */
.toast {
  position: fixed; z-index: 30;
  left: 50%; bottom: calc(var(--dock-h) + 16px);
  transform: translateX(-50%);
  max-width: min(420px, calc(100vw - 32px));
  padding: 12px 16px;
  background: rgba(38, 38, 40, 0.96);
  border: 1px solid var(--line);
  border-radius: 12px;
  font-size: 14.5px;
  box-shadow: 0 12px 34px rgba(0, 0, 0, 0.5);
  animation: rise 0.18s ease both;
}
.toast.bad { border-color: rgba(255, 69, 58, 0.55); }

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}
