@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;500;600;700&display=swap');
@import url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.css');

:root{
  /* ---- primary (original Weavo forest green) ---- */
  --green-50:rgba(26,60,43,.06); --green-200:rgba(26,60,43,.2);
  --green-600:#1A3C2B; --green-900:#0F2417;
  --mint:#9EFFBF;
  --gradient-primary: linear-gradient(135deg, var(--green-600), var(--green-900));

  --gray-0:#FFFFFF; --gray-50:#FAFAFA; --gray-100:#F2F3F1; --gray-200:#E4E7E2;
  --gray-300:#D3D7D0; --gray-500:#74796F; --gray-700:#454A41; --gray-900:#1E211C;

  --pastel-cream:#FDF6EC; --pastel-pink:#FBE8EE; --pastel-lavender:#EFE9FB;
  --gradient-hero: linear-gradient(135deg, var(--pastel-cream), var(--pastel-pink) 55%, var(--pastel-lavender));

  --bg:var(--gray-0); --surface:var(--gray-50); --text:var(--gray-900);
  --muted:var(--gray-500); --border:var(--gray-200); --border-strong:var(--gray-300);
  --primary:var(--green-600); --primary-dark:var(--green-900); --primary-soft:var(--green-50);
  --accent-warm:#FF8C69;

  --r-xs:8px; --r-sm:12px; --r-md:16px; --r-lg:24px; --r-xl:32px; --r-full:999px;
  --shadow-sm:0 1px 3px rgba(15,36,23,.08);
  --shadow-md:0 8px 24px rgba(15,36,23,.10);
  --shadow-lg:0 16px 48px rgba(15,36,23,.14);

  /* ONE PAGE FRAME, EVERY PAGE (2026-09-11). Page content is --frame wide
     with --gutter either side, so every page's left edge falls on the same
     vertical line as the Weavo logo in the top bar. Two ways to sit in the
     frame — both produce the identical edge, so pick by element type:
       • full-bleed bar or card (its own background reaches the screen edge
         on small screens): padding/margin of
         max(var(--gutter), calc((100% - var(--frame)) / 2))
       • ordinary container: max-width:var(--frame-max) + margin:0 auto
         + padding-inline:var(--gutter)
       • element already inside a gutter-padded parent: max-width:var(--frame)
     Never give a page container a max-width of its own — that is exactly
     what made every page start at a different x before this. */
  --frame:1400px;
  --gutter:32px;
  --frame-max:calc(var(--frame) + var(--gutter) * 2);
}
@media (max-width:640px){ :root{ --gutter:16px; } }
*{box-sizing:border-box;}
html,body{height:100%;}
body{
  margin:0;
  /* Double-tap zoom only. An accidental zoom on a phone is almost always a
     double tap, and turning it off also removes the browser's 300 ms wait
     before a tap counts. Pinch zoom stays — user-scalable=no is ignored by
     iOS Safari anyway and would break WCAG 1.4.4 (text at 200%), which
     matters more than usual for this audience. Descendants cannot re-enable
     it: the effective touch-action is the intersection down the tree, so
     .weavo-grid-wrap's touch-action:none still wins where it is set. */
  touch-action:manipulation;
  font-family:'Pretendard','Noto Sans KR','Segoe UI',-apple-system,BlinkMacSystemFont,Roboto,Helvetica,Arial,sans-serif;
  background:var(--bg);
  color:var(--text);
}
/* ---------- pull-to-refresh (2026-09-12) ----------
   overscroll-behavior on the VIEWPORT is what stops a pull at the top from
   being read as "refresh" — that is the html/body rule below, and it is the
   one that matters here, because on most pages of this site the DOCUMENT is
   the scroller.

   It is deliberately NOT on .main-scroll. That element is declared
   overflow-y:auto, so the browser treats it as a scroll container — but on
   most pages its content fits and it has nothing of its own to scroll, and
   the wheel/touch is supposed to CHAIN from it up to the document. Adding
   contain there stopped that chaining and left the page unable to scroll at
   all: the mouse wheel did nothing on desktop (regression, same day, found
   by dispatching a real wheel event and watching scrollTop stay at 0).

   contain belongs only on elements that really are their own scroller AND
   should swallow the overscroll — a dialog, a menu, a comment list — where
   not handing the gesture to the page behind is the whole point. Before
   adding one here, ask: does it always have its own scrollable content? If
   not, leave it off. */
html,body{overscroll-behavior:none;}
.modal-panel,
.notif-list,
.ep-disabilities-menu,
.lb-exhibit-menu,
#lightbox-caption,
#lightbox-comments-list,
#follow-list-content,
#atc-list,
#aa-list,
.mfs-panel,
.game-target{overscroll-behavior-y:contain;}
/* The constellation strip on the sky page (css/stars.css). */
.st-strip{overscroll-behavior-x:contain;}

/* A dialog used to leave the page behind it scrolling: a drag that missed
   the panel moved the page under it, and reaching the top triggered the
   refresh above. Nothing ever added the old body.no-scroll-lock class this
   replaces — :has() needs no JS at every one of the scattered
   classList.add('open') call sites, and a browser without :has() just keeps
   the old behaviour. */
body:has(.modal-overlay.open) .main-scroll{overflow:hidden;}
/* …and where the DOCUMENT is the scroller (the artwork grid, the legal
   pages), CSS alone is not enough: overflow:hidden there loses the scroll
   position and iOS jumps to the top. common.js freezes the body in place
   instead and puts the position back on close — this class is what it
   toggles. */
body.modal-open{position:fixed;left:0;right:0;width:100%;}

@keyframes appIn{from{opacity:0;}to{opacity:1;}}
@keyframes fadeIn{from{opacity:0;}to{opacity:1;}}
@keyframes cardIn{from{opacity:0;transform:translateY(6px);}to{opacity:1;transform:translateY(0);}}
@keyframes shimmer{0%{background-position:-200% 0;}100%{background-position:200% 0;}}
@keyframes popIn{from{opacity:0;transform:scale(.94);}to{opacity:1;transform:scale(1);}}

/* viewport-fit=cover lets the page background reach the notch and the home
   indicator; these keep the CONTENT out of them. Landscape side insets go on
   the shell, the top/bottom ones on the bars that actually touch the edges.
   env() falls back to 0px on everything that has no notch. */
.app{
  padding-left:env(safe-area-inset-left,0px);
  padding-right:env(safe-area-inset-right,0px);
  width:100%;
  min-height:100vh;min-height:100dvh;
  margin:0;
  background-color:var(--bg);
  display:flex;flex-direction:column;
  animation:appIn .3s ease-out;
}
/* On a wide monitor the logo was pinned to the far left and the identity
   block (language toggle, avatar, name, buttons) to the far right, metres
   apart from the page content below them. Symmetric padding holds the bar's
   contents in a centred 1400px frame — the width .hero-inner and
   .stats-bar-inner already use, so the logo lines up with the page content —
   without adding a wrapper element to all 30 page headers. The 32px floor
   keeps the original gutters on normal screens. (2026-09-11) */
.topnav{
  display:flex;align-items:center;flex-shrink:0;
  margin:0;padding:16px max(var(--gutter), calc((100% - var(--frame)) / 2));gap:28px;flex-wrap:wrap;
  padding-top:calc(16px + env(safe-area-inset-top,0px));
  background:var(--gray-0);
}
.logo{
  font-family:'Pretendard',sans-serif;font-weight:700;font-size:24px;letter-spacing:-.01em;
  color:var(--gray-900);cursor:pointer;flex-shrink:0;transition:opacity .12s ease-out;
  display:flex;align-items:center;gap:8px;background:none;border:none;padding:0;text-decoration:none;
}
.logo::before{
  content:'';display:block;width:30px;height:30px;flex-shrink:0;border-radius:50%;
  /* Downscaled 96px copy (not the 3840px, ~8MB ../logo.png): this rule loads on
     every page and the mark never renders larger than ~30px @3x. */
  background-image:url('../logo-96.png');background-size:cover;background-position:center;
}
.logo:hover{opacity:.75;}

.nav-links{display:flex;align-items:center;gap:4px;}
.nav-link{
  appearance:none;cursor:pointer;text-decoration:none;position:relative;
  font-family:'Pretendard',sans-serif;font-size:14px;font-weight:500;
  color:var(--text);background:none;border:none;padding:8px 14px;border-radius:var(--r-full);
  transition:color .12s linear,background .12s linear;
}
.nav-link:hover{color:var(--primary);background:none;}
.nav-link.active{color:var(--primary);font-weight:600;background:none;}

#identity{
  display:inline-flex;align-items:center;gap:14px;font-size:14px;flex-shrink:0;
  margin-left:auto;border:none;padding:0;
}

/* Wide enough for logo, centered nav-links, and identity to share a row
   without wrapping — grid's 1fr side columns stay equal width regardless of
   how wide the logo vs. identity block are, which is what actually centers
   the middle column (a flex-grow trick can't do this since the two sides
   rarely match in width). Below this, .topnav stays a plain wrapping flex
   row so nav-links/identity can drop to their own line instead of
   overflowing. */
@media (min-width:769px){
  .topnav{display:grid;grid-template-columns:1fr auto 1fr;column-gap:28px;}
  .logo{justify-self:start;}
  .nav-links{justify-self:center;}
  #identity{justify-self:end;margin-left:0;}
}
#myAvatar{width:30px;height:30px;border-radius:50%;object-fit:cover;display:none;cursor:pointer;border:1px solid var(--border-strong);}
#myName{font-family:'Pretendard',sans-serif;font-weight:600;font-size:14px;cursor:pointer;color:var(--text);}
#myName:not(.guest):hover{text-decoration:underline;}
#myName.guest{color:var(--muted);font-weight:500;}
.id-btn{
  appearance:none;border:none;cursor:pointer;font-family:'Pretendard',sans-serif;font-size:13px;
  padding:9px 18px;border-radius:var(--r-full);font-weight:600;line-height:1;
  transition:background .12s linear,opacity .12s linear;
}
.id-btn:active{opacity:.8;}
#loginBtn{background:var(--gradient-primary);color:#fff;box-shadow:var(--shadow-sm);}
#loginBtn:hover{opacity:.92;}
#logoutBtn{background:var(--bg);color:var(--text);border:1px solid var(--border-strong);}
#logoutBtn:hover{background:var(--surface);}
/* Admin-only header link (js/auth.js injects it for is_admin accounts). */
.admin-link{background:var(--primary-soft);color:var(--primary);text-decoration:none;display:inline-block;}
.admin-link:hover{background:var(--green-200);}
.lang-toggle{display:inline-flex;border:none;border-radius:var(--r-full);overflow:hidden;flex-shrink:0;background:var(--gray-100);}
.lang-btn{
  appearance:none;border:none;background:transparent;cursor:pointer;
  font-family:'Pretendard',sans-serif;font-size:12px;font-weight:600;padding:7px 14px;color:var(--text);
  transition:background .12s linear,color .12s linear;
}
.lang-btn.active{background:var(--primary);color:var(--bg);}
.lang-btn:hover:not(.active){background:var(--primary-soft);}

.backnav{
  display:flex;align-items:center;justify-content:space-between;flex-shrink:0;flex-wrap:wrap;gap:10px;
  padding:16px max(var(--gutter), calc((100% - var(--frame)) / 2));border-bottom:1px solid var(--border);font-size:11px;color:var(--text);
  background:var(--bg);
}
.backnav .back{
  display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:600;color:var(--primary);
  font-family:'Pretendard',sans-serif;font-size:13px;
  transition:opacity .12s ease-out;background:none;border:none;padding:0;text-decoration:none;
}
.backnav .back:hover{opacity:.7;}

.main{flex:1;min-height:0;animation:fadeIn .25s ease-out;}
.main-scroll{flex:1;min-height:0;overflow-y:auto;}

/* Visually hidden but present for screen readers/crawlers — used for a
   page's semantic <h1> where the visual design doesn't call for a separate
   heading on top of an already-descriptive section label. */
/* The `hidden` attribute must win over any display rule a class sets —
   the UA's [hidden]{display:none} loses to every author rule, so a
   ".tools{display:flex}" element that JS hides with `hidden` stays visible.
   That is how the portfolio owner tools showed for every visitor on
   2026-09-20 (the server still refused the actions). Never override this. */
[hidden]{display:none !important;}

.sr-only{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0,0,0,0);white-space:nowrap;border:0;
}
.empty-note{text-align:center;color:var(--muted);font-family:'Pretendard',sans-serif;font-size:13px;padding:48px 16px;}
.section-label{font-family:'Pretendard',sans-serif;font-size:13px;font-weight:600;color:var(--muted);margin:4px 32px 14px;}

/* ---------- shared pv-card grid tile (project list view + profile grids) ---------- */
.pv-card{
  position:relative;cursor:pointer;border-radius:var(--r-lg);overflow:hidden;background:var(--bg);border:none;
  box-shadow:var(--shadow-sm);
  transition:box-shadow .12s linear;
  display:block;color:inherit;text-decoration:none;
}
.pv-card:hover{box-shadow:var(--shadow-md);}
.pv-card-thumb{aspect-ratio:1;background:var(--bg);position:relative;}
.pv-card-thumb img{width:100%;height:100%;object-fit:cover;display:block;color:transparent;}
/* Campaign cards paint a preview canvas instead of an <img> (the reference photo stays hidden). */
.pv-card-thumb canvas{width:100%;height:100%;object-fit:cover;display:block;}
.pv-card-thumb:empty::after{
  content:'🖼';position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  font-size:22px;opacity:.2;
}
.pv-card-info{padding:10px 12px;}
.pv-card-title{font-family:'Pretendard',sans-serif;font-weight:600;font-size:13px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.pv-card-title:empty{display:none;}
.pv-card-archived-badge{
  display:inline-block;font-family:'Pretendard',sans-serif;font-size:10px;font-weight:600;
  color:var(--muted);background:var(--primary-soft);
  border-radius:999px;padding:2px 8px;vertical-align:middle;
}
.pv-card-author{font-family:'Pretendard',sans-serif;font-size:12px;color:var(--muted);margin-top:3px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.pv-card::after{
  content:'→';position:absolute;top:8px;right:8px;width:22px;height:22px;z-index:2;border-radius:var(--r-full);
  display:flex;align-items:center;justify-content:center;
  background:var(--bg);border:1px solid var(--border-strong);color:var(--primary);
  font-family:'Pretendard',sans-serif;font-size:12px;line-height:1;
  opacity:.6;transition:opacity .12s linear,background .12s linear,color .12s linear,border-color .12s linear;
}
.pv-card:hover::after{opacity:1;background:var(--primary);color:var(--bg);border-color:var(--primary);}

/* ---------- shared modal shell ---------- */
/* No backdrop-filter here (2026-09-11): it blurs the WHOLE viewport behind
   the overlay, and the GPU re-runs that blur on every repaint inside the
   dialog — a blinking caret alone is twice a second, plus once per typed
   character. Typing in #edit-profile-dialog (a near-full-viewport panel) was
   noticeably heavy on big desktop monitors because the cost scales with the
   blurred area. The dim scrim alone separates the dialog just as well, so it
   is a little darker to compensate. */
.modal-overlay{
  position:fixed;inset:0;background:rgba(15,36,23,.45);
  display:flex;align-items:center;justify-content:center;z-index:200;padding:20px;
  opacity:0;visibility:hidden;pointer-events:none;
  /* visibility is NOT interpolated — it flips at 0s. Transitioning it was
     how a dialog could end up half-applied: hide the tab inside the .15s
     and the browser parks the animation clock, leaving visibility stuck at
     its start value while .open had already made the overlay cover the
     page. The delay on the way OUT is the usual trick so the fade-out is
     still visible; going IN it applies immediately, with nothing to stall.
     common.js finishes any parked opacity transition when the tab returns. */
  transition:opacity .15s linear,visibility 0s linear .15s;
}
.modal-overlay.open{opacity:1;visibility:visible;pointer-events:auto;transition:opacity .15s linear,visibility 0s;}
.modal-panel{
  background:var(--bg);border-radius:var(--r-lg);box-shadow:var(--shadow-lg);border:none;
  max-height:90vh;overflow-y:auto;position:relative;
  transform:scale(.98);
  transition:transform .18s ease-out;
}
.modal-overlay.open .modal-panel{transform:scale(1);}
#confirm-dialog,#auth-dialog,#report-dialog{width:380px;max-width:calc(100vw - 40px);padding:26px;}
#edit-profile-dialog{
  width:calc(100vw - 40px);max-width:1600px;
  height:calc(100vh - 40px);max-height:calc(100vh - 40px);
  padding:40px;
}
#edit-profile-dialog>*{width:100%;max-width:520px;margin-left:auto;margin-right:auto;}
#confirm-dialog h3,#edit-profile-dialog h3,#report-dialog h3{font-family:'Pretendard',sans-serif;font-size:18px;font-weight:700;margin-top:0;margin-bottom:10px;color:var(--primary);}
.field{display:flex;flex-direction:column;gap:6px;margin-bottom:14px;}
.field label{font-family:'Pretendard',sans-serif;font-size:13px;font-weight:600;color:var(--text);}
.field .field-hint{font-weight:400;color:var(--muted);}
/* Live username availability hint: it sits inside the label right after the
   label text, so give it breathing room and enough weight to read at a glance. */
#ep-username-hint{margin-left:10px;font-weight:600;font-size:12.5px;}
.field input[type="text"],.field textarea,.field select{
  width:100%;padding:10px 12px;border:1px solid var(--border-strong);border-radius:var(--r-sm);
  font-size:13px;font-family:'Pretendard',sans-serif;color:var(--text);background:#fff;outline:none;transition:border-color .12s linear;
}
.field textarea{resize:vertical;min-height:64px;line-height:1.5;}
.field select{cursor:pointer;}
.field input:focus,.field textarea:focus,.field select:focus{border-color:var(--primary);}
.field-error{font-family:'Pretendard',sans-serif;font-size:11px;color:var(--accent-warm);min-height:14px;}

/* ---------- disabilities multi-select dropdown (edit-profile modal) ---------- */
/* A custom checkbox dropdown rather than a native <select multiple> — every
   row toggles on a plain left click instead of requiring ctrl/cmd-click. */
.ep-disabilities-wrap{position:relative;}
.ep-disabilities-btn{
  display:flex;align-items:center;justify-content:space-between;gap:8px;width:100%;
  padding:10px 12px;border:1px solid var(--border-strong);border-radius:var(--r-sm);background:#fff;
  font-family:'Pretendard',sans-serif;font-size:13px;color:var(--text);cursor:pointer;text-align:left;
  transition:border-color .12s linear;
}
.ep-disabilities-btn:hover,.ep-disabilities-btn.open{border-color:var(--primary);}
.ep-disabilities-summary{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--muted);}
.ep-disabilities-summary.has-value{color:var(--text);}
.ep-disabilities-caret{font-size:10px;opacity:.7;flex-shrink:0;transition:transform .12s ease;}
.ep-disabilities-btn.open .ep-disabilities-caret{transform:rotate(180deg);}
.ep-disabilities-menu{
  display:none;position:absolute;top:calc(100% + 6px);left:0;right:0;z-index:20;
  max-height:280px;overflow-y:auto;background:#fff;border:1px solid var(--border-strong);border-radius:var(--r-md);
  padding:6px;box-shadow:var(--shadow-md);
}
.ep-disabilities-menu.open{display:block;}
.ep-disabilities-divider{height:1px;background:var(--border-strong);margin:6px 4px;}
.ep-disabilities-group-label{
  font-family:'Pretendard',sans-serif;font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;
  color:var(--muted);padding:8px 8px 4px;
}
.ep-disabilities-row{
  display:flex;align-items:center;gap:8px;padding:7px 8px;border-radius:6px;cursor:pointer;
  font-family:'Pretendard',sans-serif;font-size:12.5px;color:var(--text);
}
.ep-disabilities-row:hover{background:rgba(33,30,48,.06);}
.ep-disabilities-row input{width:auto;flex-shrink:0;cursor:pointer;accent-color:var(--primary);}
.modal-actions{display:flex;gap:8px;justify-content:flex-end;padding-top:2px;}
.modal-actions button{padding:9px 18px;border-radius:var(--r-full);font-family:'Pretendard',sans-serif;font-size:13px;cursor:pointer;border:none;font-weight:600;transition:background .12s linear,opacity .12s linear;}
.modal-actions button:active{opacity:.85;}
.btn-cancel{background:transparent;color:var(--text);border:1px solid var(--border-strong);}
.btn-cancel:hover{background:rgba(33,30,48,.06);}
.btn-primary{background:var(--gradient-primary);color:#fff;font-weight:600;}
.btn-primary:hover{opacity:.92;box-shadow:none;}
.btn-primary:disabled{background:var(--border-strong);color:var(--muted);cursor:not-allowed;box-shadow:none;opacity:1;}
.ep-danger-zone{margin-top:18px;padding-top:14px;border-top:1px solid var(--border-strong);text-align:center;}
.link-danger{background:none;border:none;padding:0;font-family:'Pretendard',sans-serif;font-size:12.5px;color:var(--accent-warm);cursor:pointer;text-decoration:underline;text-underline-offset:2px;}
.link-danger:hover{opacity:.8;}
/* Onboarding exits under the forced edit-profile modal's actions (auth.js
   renderSetupExits): two pill buttons like the modal's own, the sign-up cancel in the warning colour. */
.ep-setup-exits{display:flex;gap:8px;justify-content:center;flex-wrap:wrap;margin-top:14px;padding-top:12px;border-top:1px solid var(--border);}
.ep-setup-exits button{padding:9px 18px;border-radius:var(--r-full);font-family:'Pretendard',sans-serif;font-size:13px;font-weight:600;cursor:pointer;background:transparent;color:var(--text);border:1px solid var(--border-strong);transition:background .12s linear,opacity .12s linear;}
.ep-setup-exits button:hover{background:rgba(33,30,48,.06);}
.ep-setup-exits button.danger{color:var(--accent-warm);border-color:var(--accent-warm);}
.ep-setup-exits button.danger:hover{background:#FFF1EA;}
.ep-setup-exits button:disabled{opacity:.6;cursor:default;}

/* ---------- blocked-users list (edit-profile modal) ---------- */
.ep-blocked-section{margin-top:18px;padding-top:14px;border-top:1px solid var(--border-strong);}
.ep-blocked-section h4{margin:0 0 8px;font-family:'Pretendard',sans-serif;font-size:13px;font-weight:600;color:var(--muted);}
.ep-blocked-loading{font-family:'Pretendard',sans-serif;font-size:12.5px;color:var(--muted);padding:4px 6px;}
.ep-blocked-row{padding:6px;}
.ep-blocked-unblock{
  margin-left:auto;background:none;border:none;padding:0;cursor:pointer;
  font-family:'Pretendard',sans-serif;font-size:12.5px;font-weight:600;color:var(--primary);
}
.ep-blocked-unblock:hover{text-decoration:underline;}

/* ---------- auth modal ---------- */
#auth-dialog{display:flex;flex-direction:column;gap:16px;align-items:center;text-align:center;padding:32px 26px 26px;}
#auth-dialog .auth-icon{font-size:32px;line-height:1;}
#auth-dialog h3{font-family:'Pretendard',sans-serif;font-size:18px;font-weight:700;color:var(--primary);}
#auth-dialog p{font-family:'Pretendard',sans-serif;font-size:13px;color:var(--muted);line-height:1.6;max-width:260px;}
.oauth-signin-btn{
  display:flex;align-items:center;gap:10px;justify-content:center;width:100%;padding:11px 16px;
  border-radius:var(--r-full);border:1px solid var(--border-strong);font-family:'Pretendard',sans-serif;font-size:13px;
  font-weight:600;cursor:pointer;transition:background .12s linear,border-color .12s linear,opacity .12s linear;
}
.oauth-signin-btn:active{opacity:.85;}
#google-signin-btn{background:#fff;color:var(--text);}
#google-signin-btn:hover{background:var(--primary-soft);border-color:var(--primary);}
#apple-signin-btn{background:#000;color:#fff;border-color:#000;}
#apple-signin-btn:hover{background:#1a1a1a;}
#auth-close{background:none;border:none;cursor:pointer;color:var(--muted);font-family:'Pretendard',sans-serif;font-size:12px;padding:2px 8px;}
#auth-close:hover{color:var(--primary);}

/* ---------- reusable drag/drop image picker ---------- */
.img-upload-area{
  position:relative;height:148px;border:1px dashed var(--border-strong);border-radius:var(--r-sm);
  display:flex;align-items:center;justify-content:center;overflow:hidden;cursor:pointer;
  transition:border-color .12s linear,background .12s linear;
}
.img-upload-area:hover,.img-upload-area.drag{border-color:var(--primary);background:var(--primary-soft);}
.img-upload-area.has-img{border-style:solid;border-color:var(--border-strong);}
.img-placeholder{color:var(--muted);font-family:'Pretendard',sans-serif;font-size:12px;text-align:center;pointer-events:none;line-height:1.6;}
.img-placeholder .up-icon{font-size:24px;display:block;margin-bottom:6px;}
.img-preview{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:none;pointer-events:none;}
.img-remove{
  position:absolute;top:6px;right:6px;background:rgba(15,36,23,.75);color:var(--bg);border:none;
  border-radius:50%;width:22px;height:22px;font-size:12px;cursor:pointer;display:none;
  align-items:center;justify-content:center;line-height:1;z-index:2;
}
.img-remove:hover{background:var(--primary-dark);}
.img-upload-area.has-img .img-remove{display:flex;}
.img-input{position:absolute;inset:0;opacity:0;cursor:pointer;z-index:1;}
.img-upload-area.has-img .img-input{pointer-events:none;}
/* Smaller variant for a logo (campaign forms): a shorter box, and the
   preview letterboxed instead of cropped — cover would cut a wide wordmark's ends. */

.avatar-picker.img-upload-area{width:84px;height:84px;border-radius:50%;margin:0 auto;flex-shrink:0;overflow:visible;}
.avatar-picker .img-placeholder{font-size:10px;padding:0 6px;}
.avatar-picker .img-placeholder .up-icon{font-size:18px;margin-bottom:3px;}
.avatar-picker .img-preview{border-radius:50%;}
.avatar-picker .img-remove{
  width:20px;height:20px;top:auto;bottom:-2px;right:-2px;
  box-shadow:none;
}

/* ---------- mini avatar (used anywhere a user's name/avatar links to their profile) ---------- */
.mini-avatar{
  width:20px;height:20px;border-radius:50%;overflow:hidden;flex-shrink:0;
  display:inline-flex;align-items:center;justify-content:center;
  background:var(--primary);color:var(--bg);font-family:'Pretendard',sans-serif;font-size:10px;font-weight:700;
  border:none;padding:0;cursor:pointer;text-decoration:none;
}
.mini-avatar img{width:100%;height:100%;object-fit:cover;display:block;}
.mini-avatar.lb-artist-avatar{width:52px;height:52px;font-size:20px;}

/* ---------- reusable zoom toolbar (lightbox + weavo grid + graphs) ---------- */
/* The toolbar floats over whatever is being zoomed, so once the picture is
   scaled up it sits ON the picture — and every label here is white
   (var(--bg) is pure white), with the percentage readout carrying no
   background at all. Over a light photo the glyphs simply disappeared.
   A dark translucent pill holds them at 9.3:1 even against a pure white
   image, while staying nearly invisible over the dark stage behind it.
   top/right drop by the padding so the buttons stay exactly where they were.
   No backdrop blur on purpose — same reason as .modal-overlay below. */
.zoom-toolbar{
  position:absolute;top:12px;right:12px;display:flex;align-items:center;gap:6px;z-index:5;cursor:default;
  padding:4px;border-radius:var(--r-full);background:rgba(15,20,17,.78);
}
.zoom-btn{
  width:32px;height:32px;border-radius:50%;border:1px solid rgba(247,247,245,.2);background:rgba(247,247,245,.08);color:var(--bg);
  font-size:16px;cursor:pointer;display:flex;align-items:center;justify-content:center;line-height:1;
  transition:background .12s linear;
}
.zoom-btn:hover{background:rgba(247,247,245,.18);}
.zoom-btn:active{opacity:.85;}
.zoom-btn:disabled{opacity:.35;cursor:default;pointer-events:none;}
.zoom-level{color:var(--bg);font-family:'Pretendard',sans-serif;font-size:12px;min-width:38px;text-align:center;font-variant-numeric:tabular-nums;}

/* ---------- lightbox (project.html + profile.html) ---------- */
/* Same reason as .modal-overlay above — and at 94% opacity the blur was
   invisible anyway, while still costing a viewport-sized GPU filter on every
   repaint (this overlay holds the comment box, so it is a typing surface too). */
.lightbox-overlay{
  background:rgba(15,20,17,.96);cursor:zoom-out;
  flex-direction:row;align-items:stretch;gap:1px;overflow:hidden;
}
.lightbox-stage{
  position:relative;flex:1;min-width:0;height:100%;box-sizing:border-box;padding:24px;
  display:flex;align-items:center;justify-content:center;overflow:hidden;
  transform:scale(.98);opacity:0;transition:transform .2s ease-out,opacity .18s linear;
}
.lightbox-overlay.open .lightbox-stage{transform:scale(1);opacity:1;}
.lightbox-overlay img{
  max-width:100%;max-height:100%;object-fit:contain;border-radius:0;box-shadow:none;border:1px solid rgba(247,247,245,.15);
  cursor:zoom-in;touch-action:none;transition:transform .15s ease-out;
}
.lightbox-overlay img.zoomed{cursor:grab;}
.lightbox-overlay img.dragging{cursor:grabbing;transition:none;}

/* Tap the artwork image to blow it up edge-to-edge (caption + page chrome
   drop away); tap again, or press Escape, to come back. .img-fs is set (by
   js/lightbox.js) on whichever element wraps the shared #lightboxStage
   markup — the lightbox modal (#lightbox-modal) on project/profile, or
   #artworkStage on the standalone artwork page. */
.img-fs #lightbox-caption{display:none;}
.img-fs #lightboxStage{
  position:fixed;inset:0;z-index:250;width:auto;height:auto;flex:none;
  padding:0;transform:none;background:#0b0f0d;
  display:flex;align-items:center;justify-content:center;
}
.img-fs #lightboxStage #lightbox-img{
  max-width:100vw;max-height:100dvh;width:auto;height:auto;object-fit:contain;cursor:zoom-out;
}
body.lb-img-fs-lock{overflow:hidden;}
#lightbox-caption{
  color:var(--bg);font-size:13px;background:rgba(247,247,245,.06);padding:20px;border-radius:0;border-left:1px solid rgba(247,247,245,.15);
  flex-shrink:0;width:320px;max-width:36vw;height:100%;box-sizing:border-box;
  overflow-y:auto;text-align:left;
}
@media (max-width:760px){
  .lightbox-overlay{flex-direction:column;gap:1px;}
  .lightbox-stage{width:100%;height:auto;flex:1;padding:12px;}
  #lightbox-caption{
    width:100%;max-width:92vw;max-height:46vh;height:auto;text-align:center;border-left:none;border-top:1px solid rgba(247,247,245,.15);
  }
}
#lightbox-caption.hidden{display:none;}
#lightbox-cap-title{font-family:'Pretendard',sans-serif;font-weight:700;font-size:15px;margin-bottom:3px;}
#lightbox-cap-title:empty,#lightbox-cap-desc:empty,#lightbox-cap-meta:empty{display:none;}
#lightbox-cap-meta{color:rgba(247,247,245,.55);font-family:'Pretendard',sans-serif;font-size:11.5px;font-weight:600;}
#lightbox-cap-desc{color:rgba(247,247,245,.7);font-size:12.5px;line-height:1.5;margin-top:4px;}
/* "Row r, column c of the n×n cut" under the title when a mosaic cell opened
   this artwork (lightbox.js renderLightboxPieceNote): a mini grid marks the piece. */
#lightbox-cap-piece{display:flex;align-items:center;gap:8px;margin-top:6px;color:rgba(247,247,245,.7);font-family:'Pretendard',sans-serif;font-size:11.5px;font-weight:600;}
.lb-piece-grid{display:grid;gap:1px;width:28px;height:28px;flex-shrink:0;}
.lb-piece-grid i{display:block;background:rgba(247,247,245,.22);border-radius:1px;}
.lb-piece-grid i.on{background:var(--mint);}
/* Artwork page only: "12/49 pieces in the campaign mosaic" with a small bar (artwork.js renderArtworkPieceUsage). */
.piece-usage{display:flex;align-items:center;gap:10px;margin:6px 0 2px;font-family:'Pretendard',sans-serif;font-size:12.5px;color:rgba(247,247,245,.75);}
.piece-usage-bar{flex:0 0 120px;height:6px;border-radius:3px;background:rgba(247,247,245,.18);overflow:hidden;}
.piece-usage-bar span{display:block;height:100%;background:var(--mint);}
#lightbox-artist-card{display:flex;align-items:center;gap:10px;margin:8px 0 4px;}
.lb-artist-info{min-width:0;flex:1;text-align:left;}
.lb-artist-name{
  display:block;background:none;border:none;padding:0;font:inherit;color:var(--bg);
  font-weight:700;font-size:14px;cursor:pointer;text-align:left;text-decoration:none;
}
.lb-artist-name:hover{text-decoration:underline;}
.lb-artist-name:empty{display:none;}
.lb-artist-follow-btn{
  flex-shrink:0;appearance:none;cursor:pointer;border:none;
  font-family:'Pretendard',sans-serif;font-size:12px;font-weight:600;
  padding:6px 14px;border-radius:16px;background:var(--mint);color:var(--primary-dark);
  transition:background .12s linear,opacity .12s linear;
}
.lb-artist-follow-btn:hover{opacity:.85;}
.lb-artist-follow-btn.following{background:transparent;color:var(--bg);border:1px solid rgba(247,247,245,.3);}
.lb-artist-follow-btn.following:hover{background:rgba(247,247,245,.1);opacity:1;}
#lightbox-artist-about{
  margin:4px 0 10px;padding-top:10px;border-top:1px solid rgba(247,247,245,.15);
  display:none;text-align:left;
}
#lightbox-artist-about.visible{display:block;}
.lb-artist-about-label{
  color:var(--bg);font-family:'Pretendard',sans-serif;font-size:12px;font-weight:600;
  margin-bottom:6px;
}
.lb-artist-bio{color:rgba(247,247,245,.75);font-size:12.5px;line-height:1.6;}
#lightbox-cap-link{color:var(--mint);font-size:12.5px;display:inline-block;margin-top:6px;}
#lightbox-cap-link:empty{display:none;}
#lightbox-cap-link:hover{text-decoration:underline;}
.lightbox-actions{display:flex;flex-wrap:wrap;gap:8px;justify-content:flex-start;margin-top:8px;}
@media (max-width:760px){
  #lightbox-artist-card,.lightbox-actions{justify-content:center;}
}
#lightbox-country-map{
  display:none;margin-top:14px;padding-top:14px;border-top:1px solid rgba(247,247,245,.15);
}
.lb-country-map-label{
  color:var(--bg);font-family:'Pretendard',sans-serif;font-size:12px;font-weight:600;
  margin-bottom:6px;text-align:center;
}
#lightbox-country-map svg{width:100%;height:auto;display:block;}
.lb-country{fill:rgba(247,247,245,.14);stroke:rgba(0,0,0,.35);stroke-width:.4;}
.lb-country.active{fill:var(--mint);}
.lb-action-btn{
  display:inline-flex;align-items:center;gap:6px;
  padding:7px 14px;border-radius:20px;border:1px solid rgba(247,247,245,.2);cursor:pointer;
  background:transparent;color:var(--bg);font-family:'Pretendard',sans-serif;font-size:13px;font-weight:600;
  transition:background .12s linear,color .12s linear,border-color .12s linear;
}
.lb-action-btn:hover{background:rgba(247,247,245,.1);}
.lb-action-btn:disabled{opacity:.5;cursor:default;}
.lb-action-btn .icon::before{content:"♡";}
#lb-like-btn.active{background:var(--accent-warm);color:var(--primary-dark);border-color:var(--accent-warm);}
#lb-like-btn.active .icon::before{content:"♥";}
#lb-comments-btn .icon::before{content:"💬";}
#lb-comments-btn.active{background:rgba(247,247,245,.16);}
#lb-exhibit-btn .icon::before{content:"🗂";}
#lb-edit-btn .icon::before{content:"✎";}
#lb-delete-btn .icon::before{content:"🗑";}
#lb-play-btn .icon::before{content:"🎮";}
#lb-play-btn{text-decoration:none;}
#lb-color-btn .icon::before{content:"\1F3A8";}
#lb-color-btn{text-decoration:none;}
#lb-delete-btn:hover{background:var(--accent-warm);color:var(--primary-dark);border-color:var(--accent-warm);}
#lb-report-btn .icon::before{content:"🚩";}
#lb-report-btn:hover{background:rgba(247,247,245,.1);}
#lb-block-btn .icon::before{content:"🚫";}
#lb-block-btn:hover{background:rgba(247,247,245,.1);}
#lb-block-btn.blocked{background:rgba(247,247,245,.16);}

/* ---------- report/delete button group on a lightbox comment row ---------- */
.lbc-head-actions{margin-left:auto;display:flex;align-items:center;gap:10px;}
.lbc-head-actions .lbc-delete{margin-left:0;}
.lbc-report{background:none;border:none;color:rgba(247,247,245,.5);font-size:11px;cursor:pointer;padding:0;font-family:inherit;}
.lbc-report:hover{color:var(--bg);}

/* ---------- add-to-exhibition multi-select dropdown (lightbox sidebar) ---------- */
.lb-exhibit-wrap{position:relative;display:inline-flex;}
.lb-exhibit-caret{font-size:10px;opacity:.7;margin-left:1px;}
.lb-exhibit-btn.open{background:rgba(247,247,245,.16);}
.lb-exhibit-menu{
  display:none;position:absolute;top:calc(100% + 8px);left:0;z-index:20;
  width:max(240px,100%);max-width:260px;max-height:280px;overflow-y:auto;
  background:var(--primary-dark);border:1px solid rgba(247,247,245,.2);border-radius:10px;
  padding:8px;box-shadow:0 10px 28px rgba(0,0,0,.4);
}
.lb-exhibit-menu.open{display:block;}
.lb-exhibit-row{
  display:flex;align-items:center;gap:8px;padding:7px 8px;border-radius:6px;cursor:pointer;
  font-family:'Pretendard',sans-serif;font-size:12.5px;color:var(--bg);
}
.lb-exhibit-row:hover{background:rgba(247,247,245,.08);}
.lb-exhibit-row input{width:auto;flex-shrink:0;}
.lb-exhibit-row span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.lb-exhibit-empty{font-family:'Pretendard',sans-serif;font-size:12px;color:rgba(247,247,245,.55);padding:6px 8px 10px;}
.lb-exhibit-loading{font-family:'Pretendard',sans-serif;font-size:12px;color:rgba(247,247,245,.55);padding:6px 8px;}
.lb-exhibit-new{display:flex;gap:6px;margin-top:6px;padding-top:8px;border-top:1px solid rgba(247,247,245,.15);}
.lb-exhibit-new input{
  flex:1;min-width:0;padding:7px 9px;border-radius:6px;border:1px solid rgba(247,247,245,.25);
  background:rgba(247,247,245,.06);color:var(--bg);font-family:'Pretendard',sans-serif;font-size:12px;outline:none;
}
.lb-exhibit-new input::placeholder{color:rgba(247,247,245,.4);}
.lb-exhibit-new input:focus{border-color:var(--mint);}
.lb-exhibit-new button{
  flex-shrink:0;padding:7px 12px;border-radius:6px;border:none;cursor:pointer;
  background:var(--mint);color:var(--primary-dark);font-family:'Pretendard',sans-serif;font-size:12px;font-weight:600;
}
.lb-exhibit-new button:disabled{opacity:.5;cursor:default;}

.lightbox-comments{display:none;margin-top:10px;text-align:left;}
.lightbox-comments.open{display:block;}
#lightbox-comments-list{
  max-height:200px;overflow-y:auto;display:flex;flex-direction:column;gap:10px;
  margin-bottom:10px;padding-right:2px;
}
.lb-comments-empty{color:rgba(247,247,245,.5);font-family:'Pretendard',sans-serif;font-size:13px;text-align:center;padding:8px 0;}
.lb-comment{font-size:12.5px;}
.lb-comment-reply{margin-left:26px;margin-top:8px;}
.lbc-head{display:flex;align-items:center;gap:6px;margin-bottom:3px;}
.lbc-author{
  color:var(--bg);font-weight:600;font-size:12.5px;background:none;border:none;padding:0;font-family:inherit;cursor:pointer;text-decoration:none;
}
.lbc-author:hover{text-decoration:underline;}
.lbc-time{color:rgba(247,247,245,.5);font-size:11px;}
.lbc-delete{margin-left:auto;background:none;border:none;color:rgba(247,247,245,.5);font-size:11px;cursor:pointer;padding:0;font-family:inherit;}
.lbc-delete:hover{color:var(--accent-warm);}
.lbc-body{color:rgba(247,247,245,.9);line-height:1.5;word-break:break-word;}
.lbc-reply-btn{background:none;border:none;color:var(--mint);font-size:11.5px;cursor:pointer;padding:4px 0 0;font-family:inherit;}
.lbc-reply-btn:hover{text-decoration:underline;}
.lbc-reply-form{display:flex;gap:6px;margin-top:6px;align-items:flex-start;}
.lbc-replies{display:flex;flex-direction:column;}
#lightbox-comment-form{display:flex;gap:8px;align-items:flex-start;}
#lightbox-comment-input,.lbc-reply-form textarea{
  font-family:inherit;font-size:12.5px;padding:8px 10px;border-radius:8px;border:1px solid rgba(247,247,245,.2);
  background:rgba(247,247,245,.06);color:var(--bg);resize:vertical;outline:none;
}
#lightbox-comment-input{flex:1;min-height:38px;}
.lbc-reply-form textarea{flex:1;min-height:36px;}
#lightbox-comment-input::placeholder,.lbc-reply-form textarea::placeholder{color:rgba(247,247,245,.4);}
#lightbox-comment-input:focus,.lbc-reply-form textarea:focus{border-color:var(--mint);}
#lightbox-comment-post,.lbc-reply-form button{
  background:var(--bg);color:var(--primary-dark);border:none;border-radius:16px;padding:8px 14px;font-family:'Pretendard',sans-serif;font-size:12px;font-weight:700;
  cursor:pointer;flex-shrink:0;transition:background .12s linear;
}
#lightbox-comment-post:hover,.lbc-reply-form button:hover{background:var(--mint);}
#lightbox-comment-post:disabled{opacity:.5;cursor:default;}

/* ---------- legal (about / privacy / disclaimer) ---------- */
/* Text-only pages are the one documented exception to the shared frame: a
   760px reading measure left-aligned inside a 1400px frame left most of a
   wide monitor empty beside it, so the column is CENTRED instead (the look
   these pages always had). Still built from --gutter so the side padding
   matches every other page. (2026-09-11) */
.legal-page{max-width:calc(760px + var(--gutter) * 2);margin:0 auto;padding:36px var(--gutter) 64px;animation:fadeIn .3s ease-out;}
.legal-page h1{font-family:'Pretendard',sans-serif;font-size:30px;font-weight:700;margin:0 0 4px;color:var(--primary);letter-spacing:-.01em;}
.legal-updated{font-family:'Pretendard',sans-serif;font-size:12px;color:var(--muted);margin:0 0 24px;}
.legal-body{font-size:14px;line-height:1.7;color:var(--text);max-width:680px;}
.legal-body h2{font-family:'Pretendard',sans-serif;font-size:16px;font-weight:700;margin:26px 0 8px;color:var(--primary);}
.legal-body h2:first-child{margin-top:0;}
.legal-body p{margin:0 0 12px;color:var(--muted);}
.legal-body ul,.legal-body ol{margin:0 0 12px;padding-left:20px;color:var(--muted);}
.legal-body li{margin-bottom:5px;}
.legal-body li strong{color:var(--text);}
.legal-body a{color:var(--primary);}
@media (max-width:640px){ .legal-page{padding:28px var(--gutter) 48px;} }

#legal-footer{
  flex-shrink:0;
  display:flex;align-items:center;justify-content:center;gap:10px;
  padding:14px 32px calc(14px + env(safe-area-inset-bottom,0px));border-top:none;
  background:var(--surface);
}
#legal-footer a,#legal-footer button{
  background:none;border:none;padding:0;cursor:pointer;
  color:var(--muted);font-family:'Pretendard',sans-serif;font-size:12px;
  transition:color .12s linear;text-decoration:none;
}
#legal-footer a:hover,#legal-footer button:hover{color:var(--primary);}
#legal-footer .sep{color:var(--border-strong);font-size:11px;}
@media (max-width:640px){ #legal-footer{padding:12px 16px;} }

#toast{
  position:fixed;left:50%;bottom:calc(24px + env(safe-area-inset-bottom,0px));transform:translateX(-50%) translateY(8px);
  background:var(--gray-900);color:#fff;font-family:'Pretendard',sans-serif;font-size:13px;padding:10px 16px;border-radius:var(--r-sm);
  opacity:0;pointer-events:none;transition:opacity .18s linear,transform .18s linear;z-index:300;
  box-shadow:var(--shadow-md);
}
#toast.show{opacity:1;transform:translateX(-50%) translateY(0);}

a:focus-visible, button:focus-visible, [role="button"]:focus-visible,
input:focus-visible, textarea:focus-visible, select:focus-visible{
  outline:2px solid var(--primary);outline-offset:2px;
}

html[lang="ko"] * {
  font-family:'Pretendard','Noto Sans KR','Segoe UI',sans-serif !important;
  letter-spacing:normal !important;
}

@media (prefers-reduced-motion: reduce){
  *,*::before,*::after{
    animation-duration:.001s !important;animation-iteration-count:1 !important;
    transition-duration:.001s !important;scroll-behavior:auto !important;
  }
}

/* ============ MOBILE FULLSCREEN POPUP ============ */
/* Narrow screens only (inert above 640px, so desktop is untouched). Two
   flavours share the .mfs-* marks:
   • <body data-mobile-fs> — a whole standalone page (Projects, Network)
     drops its chrome and presents as a full-viewport surface; the injected
     "←" (top-left, clear of each page's own top-right controls) returns
     wherever the user came from.
   • .mfs-panel / .mfs-panel.mfs-open — an in-page section (the profile
     page's Projects / Network) expands to fill the viewport on demand; the
     injected "×" (top-right) collapses it back inline. */
.mfs-close-btn{
  display:none;position:fixed;top:calc(12px + env(safe-area-inset-top,0px));right:12px;z-index:401;
  width:38px;height:38px;padding:0;border-radius:50%;
  align-items:center;justify-content:center;cursor:pointer;
  background:rgba(247,247,245,.95);border:1px solid var(--border-strong);
  color:var(--text);font-size:18px;line-height:1;box-shadow:0 2px 10px rgba(0,0,0,.14);
}
.mfs-close-btn.mfs-back-btn{right:auto;left:12px;}
.mfs-expand-btn{display:none;}

@media (max-width:640px){
  /* --- standalone page --- */
  body[data-mobile-fs] .topnav,
  body[data-mobile-fs] .backnav,
  body[data-mobile-fs] #legal-footer{display:none;}
  body[data-mobile-fs] .app{height:100dvh;min-height:0;}
  body[data-mobile-fs] .mfs-close-btn{display:flex;}
  /* keep page content from tucking under the floating back control */
  body[data-mobile-fs] .main-scroll.home-scroll{scroll-snap-type:none;padding-top:40px;}
  /* network page: hand the graph the whole screen */
  body[data-mobile-fs] .network-page{padding:0;}
  body[data-mobile-fs] .network-page-title{display:none;}
  /* keep the "what is this?" explainer reachable on the fullscreen mobile
     graph — float its trigger into a corner instead of stacking it above. */
  body[data-mobile-fs] .network-page .page-title-row{position:fixed;left:12px;bottom:calc(12px + env(safe-area-inset-bottom,0px));z-index:150;margin:0;}
  body[data-mobile-fs] .network-page .info-btn{background:rgba(247,247,245,.95);box-shadow:0 2px 10px rgba(0,0,0,.14);}
  body[data-mobile-fs] .network-graph-panel{height:100dvh;margin:0;border:none;border-radius:0;}

  /* --- in-page expandable panel (profile) --- */
  .mfs-panel .mfs-expand-btn{
    display:inline-flex;align-items:center;gap:6px;cursor:pointer;
    margin:2px 0 14px;padding:8px 15px;border-radius:18px;
    background:var(--primary);color:var(--bg);border:none;
    font-family:'Pretendard',sans-serif;font-size:12px;font-weight:600;
  }
  .mfs-panel.mfs-open{
    position:fixed;inset:0;z-index:400;
    max-width:none;margin:0;padding:16px;
    display:flex;flex-direction:column;
    background:var(--bg);overflow-y:auto;-webkit-overflow-scrolling:touch;
    animation:fadeIn .2s ease-out;
  }
  .mfs-panel.mfs-open .mfs-expand-btn{display:none;}
  .mfs-panel.mfs-open .mfs-close-btn{display:flex;}
  .mfs-panel.mfs-open .profile-graph-desc{margin-right:44px;}
  .mfs-panel.mfs-open .profile-graph-crumb{max-width:calc(100% - 60px);}
  .mfs-panel.mfs-open .profile-graph-panel{flex:1;min-height:0;height:auto;}
  .mfs-panel.mfs-open .profile-projects-grid{grid-template-columns:repeat(2,1fr);}
  body.mfs-lock{overflow:hidden;}
  body.mfs-lock .main-scroll{overflow:hidden;}
}

@media (max-width:640px){
  .topnav,.backnav{padding-left:16px;padding-right:16px;}
  .topnav{gap:14px 16px;margin:0;padding-top:12px;padding-bottom:12px;}
  /* Five nav links no longer reliably fit one row at phone widths — let
     them wrap onto a second line instead of overflowing the viewport. */
  .nav-links{gap:6px 10px;flex-wrap:wrap;justify-content:center;}
  #identity{gap:10px;}
  #loginBtn,#logoutBtn{padding:7px 12px;}
  .section-label{margin-left:16px;margin-right:16px;}
}

/* ============ "WHAT IS THIS?" INFO POPUP ============ */
/* The Network and Exhibitions explainers used to be columns on the landing
   page's "How It Works". They now live on the surfaces they describe
   (/network, /exhibitions) as a popup behind a small [data-info-open]
   trigger next to the page title. Wiring: js/common.js. */
.page-title-row{display:flex;align-items:center;gap:14px;flex-wrap:wrap;margin-bottom:10px;}
.page-title-row > h1{margin-bottom:0;}
.info-btn{
  flex-shrink:0;appearance:none;cursor:pointer;background:none;
  border:1px solid var(--border-strong);border-radius:16px;padding:5px 12px;
  font-family:'Pretendard',sans-serif;font-size:12px;font-weight:600;color:var(--muted);
  transition:color .12s linear,border-color .12s linear;
}
.info-btn:hover{color:var(--primary);border-color:var(--primary);}

#info-dialog{width:460px;max-width:calc(100vw - 40px);padding:26px;}
#info-dialog .feature-tag{
  display:block;font-family:'Pretendard',sans-serif;font-size:12px;font-weight:700;
  letter-spacing:.04em;text-transform:uppercase;color:var(--muted);margin:0 0 10px;
}
#info-dialog h3{
  font-family:'Pretendard',sans-serif;font-weight:700;font-size:19px;letter-spacing:-.01em;
  margin:0 0 12px;color:var(--primary);
}
#info-dialog p{font-family:'Pretendard',sans-serif;font-size:14px;line-height:1.65;color:var(--text);margin:0 0 18px;}
#info-dialog .feature-diagram{margin:0;}
.info-dialog-close{
  position:absolute;top:12px;right:12px;width:30px;height:30px;border-radius:50%;padding:0;
  display:flex;align-items:center;justify-content:center;cursor:pointer;
  background:none;border:1px solid var(--border-strong);color:var(--muted);font-size:13px;
  transition:color .12s linear,border-color .12s linear;
}
.info-dialog-close:hover{color:var(--primary);border-color:var(--primary);}

/* ---------- explainer diagrams (shared: landing "How It Works" + info popups) ---------- */
.feature-diagram{margin-bottom:20px;display:flex;align-items:center;justify-content:center;}
.feature-diagram-panel{width:100%;max-width:440px;border:1px solid var(--border-strong);border-radius:2px;overflow:hidden;}
.feature-diagram-panel svg{width:100%;height:auto;display:block;}
.feature-diagram-dark{background:var(--primary-dark);padding:18px;}
.feature-diagram-light{background:#fff;padding:18px;}
/* network diagram — mirrors the real Network page's dark graph panel */
.fd-link{stroke:rgba(247,247,245,.3);stroke-width:1.4px;}
.fd-link-mint{stroke:var(--mint);stroke-width:1.6px;}
.fd-node{fill:var(--bg);stroke:rgba(247,247,245,.35);stroke-width:1.5px;}
.fd-node-mint{stroke:var(--mint);stroke-width:2.5px;}
.fd-node-center{stroke:var(--mint);stroke-width:3px;}
/* collections diagram — mirrors the profile page's collection board cards */
.fd-card-body{fill:var(--bg);stroke:var(--border-strong);stroke-width:1px;}
.fd-swatch{opacity:.92;}
.fd-swatch-empty{fill:none;stroke:var(--border-strong);stroke-width:1px;stroke-dasharray:3 3;}
.fd-card-title{font-family:'Pretendard',sans-serif;font-weight:700;font-size:12px;fill:var(--primary);}
.fd-card-meta{font-family:'Pretendard',sans-serif;font-size:9px;fill:var(--muted);}
.fd-card-new{fill:none;stroke:var(--border-strong);stroke-width:1.5px;stroke-dasharray:5 4;}
.fd-card-plus{font-family:'Pretendard',sans-serif;font-weight:700;font-size:34px;fill:var(--border-strong);}

/* ============ NOTIFICATION BELL + FEED (js/notifications.js) ============ */
.notif-wrap{position:relative;display:inline-flex;flex-shrink:0;}
.notif-bell{
  appearance:none;border:none;background:none;cursor:pointer;position:relative;
  width:34px;height:34px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;
  color:var(--text);transition:background .12s linear;
}
.notif-bell:hover{background:var(--primary-soft);}
.notif-bell[aria-expanded="true"]{background:var(--primary-soft);color:var(--primary);}
.notif-badge{
  display:none;position:absolute;top:1px;right:0;min-width:16px;height:16px;padding:0 4px;
  border-radius:9px;background:var(--accent-warm);color:#fff;box-sizing:border-box;
  font-family:'Pretendard',sans-serif;font-size:10px;font-weight:700;line-height:16px;text-align:center;
  border:1.5px solid var(--bg);
}
.notif-badge.show{display:block;}

.notif-panel{
  display:none;position:absolute;top:calc(100% + 8px);right:0;z-index:60;
  width:340px;max-width:calc(100vw - 32px);
  background:#fff;border:none;border-radius:var(--r-md);
  box-shadow:var(--shadow-lg);overflow:hidden;
  flex-direction:column;
}
.notif-panel.open{display:flex;}
.notif-panel-head{
  display:flex;align-items:center;justify-content:space-between;gap:8px;
  padding:12px 14px;border-bottom:1px solid var(--border);
}
.notif-panel-title{font-family:'Pretendard',sans-serif;font-weight:700;font-size:14px;color:var(--text);}
.notif-mark-all{
  appearance:none;border:none;background:none;cursor:pointer;
  font-family:'Pretendard',sans-serif;font-size:12px;font-weight:600;color:var(--primary);padding:2px 4px;
}
.notif-mark-all:hover{text-decoration:underline;}
/* Push switch (js/push.js), inserted before "mark all read". Only present
   when the browser supports push AND an admin has configured the keys, so
   the header is usually just title + mark-all as before. */
.push-toggle{margin-left:auto;color:var(--muted);white-space:nowrap;}
.push-toggle.on{color:var(--primary);}
.push-toggle:disabled{cursor:default;opacity:.55;text-decoration:none;}
.push-toggle:disabled:hover{text-decoration:none;}
.notif-list{max-height:min(420px,70vh);overflow-y:auto;}
.notif-item{
  display:flex;align-items:flex-start;gap:10px;padding:11px 14px;text-decoration:none;
  border-bottom:1px solid var(--border);transition:background .12s linear;
}
.notif-item:last-child{border-bottom:none;}
.notif-item:hover{background:rgba(33,30,48,.05);}
.notif-item.unread{background:var(--primary-soft);}
.notif-item.unread:hover{background:rgba(26,60,43,.13);}
.notif-avatar{
  width:34px;height:34px;border-radius:50%;object-fit:cover;flex-shrink:0;
  border:1px solid var(--border-strong);
}
.notif-avatar-fallback{
  display:flex;align-items:center;justify-content:center;background:var(--primary-soft);
  font-family:'Pretendard',sans-serif;font-weight:700;font-size:13px;color:var(--primary);
}
.notif-item-body{flex:1;min-width:0;}
.notif-item-text{
  font-family:'Pretendard',sans-serif;font-size:12.5px;line-height:1.45;color:var(--text);
  display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;
}
.notif-item-text strong{font-weight:600;}
.notif-item-time{font-family:'Pretendard',sans-serif;font-size:11px;color:var(--muted);margin-top:3px;}
/* ---------- admin announcement rows ----------
   A message from the site rather than from a person: the Weavo mark instead
   of an avatar, the admin's headline in place of "{name} did X", and the
   body under it. supabase_admin_broadcast.sql. */
.notif-announce-mark{background:var(--primary);color:var(--bg);font-weight:700;}
/* Everything else in this panel is one short generated sentence, so it is
   clamped to three lines. An announcement is the opposite: a person wrote
   it, it can run to 500 characters, and this panel is the ONLY place the
   full text exists — tapping it goes to the home page, not to the message.
   So it is not clamped. The list scrolls. */
.notif-announcement .notif-item-text{display:block;-webkit-line-clamp:none;overflow:visible;}
.notif-announce-body{
  margin-top:3px;color:var(--muted);font-size:12.5px;line-height:1.45;
  white-space:pre-wrap;overflow-wrap:anywhere;
}
.notif-item-dot{
  width:7px;height:7px;border-radius:50%;background:var(--accent-warm);flex-shrink:0;margin-top:6px;
}
.notif-empty{
  display:none;text-align:center;color:var(--muted);
  font-family:'Pretendard',sans-serif;font-size:12.5px;padding:40px 16px;
}
.notif-empty.show{display:block;}

@media (max-width:640px){
  .notif-panel{
    position:fixed;top:auto;right:8px;left:8px;width:auto;max-width:none;
  }
  .notif-list{max-height:60vh;}
}

/* ---------- "how a mosaic comes together" step card ----------
   Moved here from home.css when the How-It-Works panel left the landing
   page for /about (2026-09-09): the about pages only load base.css. */
.feature-col{max-width:760px;padding:32px;background:var(--surface);border-radius:var(--r-lg);}
.about-hiw{margin:20px 0 24px;}
.feature-tag{display:block;font-family:'Pretendard',sans-serif;font-size:12px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--muted);margin-bottom:12px;}
.feature-col h3{font-family:'Pretendard',sans-serif;font-weight:700;font-size:20px;letter-spacing:-.01em;margin:0 0 12px;color:var(--primary);}
.hiw-intro{font-family:'Pretendard',sans-serif;color:var(--muted);font-size:15px;line-height:1.6;margin:0 0 6px;}
.hiw-steps-compact{list-style:none;margin:0;padding:0;}
.hiw-steps-compact li{display:flex;gap:14px;padding:14px 0;border-bottom:1px solid var(--border);}
.hiw-steps-compact li:first-child{padding-top:0;}
.hiw-steps-compact li:last-child{border-bottom:none;padding-bottom:0;}
.hiw-num-sm{flex:0 0 auto;font-family:'Pretendard',sans-serif;font-weight:700;font-size:20px;color:var(--border-strong);width:30px;line-height:1.35;}
.hiw-steps-compact h4{font-family:'Pretendard',sans-serif;font-weight:700;font-size:14.5px;margin:0 0 4px;color:var(--primary);}
.hiw-steps-compact p{font-family:'Pretendard',sans-serif;font-size:13px;line-height:1.55;color:var(--muted);margin:0;}
@media (max-width:640px){ .feature-col{padding:22px 18px;} }

/* ---------- entry banners (home hero, campaign header) ----------
   Lives here, not in game.css, because these appear on the HOME and CAMPAIGN
   pages — which never load game.css. Both are hidden by common.js when the
   game site option is off. */
.game-banner{
  /* flex + fit-content: its own line (inline-flex would sit beside the
     link above it), but only as wide as its contents. */
  display:flex;align-items:center;gap:10px;margin-top:12px;padding:9px 12px;
  width:fit-content;max-width:100%;
  border:none;border-radius:var(--r-md);
  background:var(--gradient-primary);color:#fff;text-decoration:none;
  box-shadow:0 3px 12px rgba(15,36,23,.15);
  transition:transform .14s ease-out,box-shadow .14s ease-out,filter .14s linear;
}
.game-banner:hover{transform:translateY(-1px);box-shadow:0 6px 18px rgba(15,36,23,.2);filter:brightness(1.06);}
.game-banner:active{transform:translateY(0);}
/* Two banners side by side on the home hero (find the piece, colour by
   number); they wrap to one column when the space runs out. */
.game-banners{display:flex;flex-wrap:wrap;gap:10px;margin-top:12px;}
/* Equal widths: both grow from the same basis, so they match on one row and
   each fills the line when they wrap (2026-09-21, user request). */
.game-banners .game-banner{margin-top:0;flex:1 1 220px;width:auto;max-width:none;}
.game-banner-color{background:linear-gradient(135deg,#2E5AAC,#1B3A73);box-shadow:0 3px 12px rgba(27,58,115,.18);}
.game-banner-color:hover{box-shadow:0 6px 18px rgba(27,58,115,.24);}
/* The icon sits in its own translucent disc so it reads as a badge rather
   than an emoji dropped into a sentence. */
.game-banner-icon{
  display:flex;align-items:center;justify-content:center;flex-shrink:0;
  width:30px;height:30px;border-radius:50%;
  background:rgba(255,255,255,.18);font-size:15px;line-height:1;
}
.game-banner-text{display:flex;flex-direction:column;min-width:0;flex:1;gap:1px;}
.game-banner-text b{font-family:'Pretendard',sans-serif;font-size:13.5px;font-weight:700;color:#fff;}
.game-banner-text span{font-family:'Pretendard',sans-serif;font-size:11.5px;color:rgba(255,255,255,.8);}
.game-banner-go{
  font-size:13px;color:#fff;flex-shrink:0;
  width:22px;height:22px;border-radius:50%;background:rgba(255,255,255,.18);
  display:flex;align-items:center;justify-content:center;
  transition:transform .14s ease-out;
}
.game-banner:hover .game-banner-go{transform:translateX(3px);}
@media (prefers-reduced-motion: reduce){
  .game-banner,.game-banner-go{transition:none;}
  .game-banner:hover{transform:none;}
  .game-banner:hover .game-banner-go{transform:none;}
}
.game-cta{text-decoration:none;display:inline-flex;align-items:center;gap:6px;}

/* The star a finished game awards — one strip on both result screens
   (2026-09-21). Hidden until the record is saved, so it never promises a
   star to a signed-out player. */
.star-award{
  display:inline-flex;align-items:center;gap:10px;flex-wrap:wrap;justify-content:center;
  margin:10px auto 0;padding:10px 18px;border-radius:var(--r-full);
  background:linear-gradient(135deg,rgba(255,217,142,.18),rgba(158,199,255,.18));
  border:1px solid rgba(255,217,142,.45);
  font-family:'Pretendard',sans-serif;font-size:13.5px;font-weight:600;color:var(--text);
}
.star-award[hidden]{display:none;}
/* "Shining in N skies" under an artwork — the artist's half of the games. */
.artwork-stars{font-family:'Pretendard',sans-serif;font-size:12.5px;font-weight:600;color:#B07B1E;margin-top:6px;}
.star-award-mark{color:#E8B84B;font-size:16px;line-height:1;}
.star-award-link{color:var(--primary);text-decoration:none;font-weight:700;}
.star-award-link:hover{text-decoration:underline;}
@media (max-width:640px){
  /* Phone: the column is already narrow, so let it fill. */
  .game-banner{width:auto;display:flex;}
}

/* Medal holders, bottom-right corner of the picture. It floats over the
   artwork, so it carries its own dark pill (a bare list vanishes on a
   bright image) and never takes more than a third of the width. */
.lb-medals{
  position:absolute;right:12px;bottom:12px;z-index:2;max-width:min(46%,280px);
  padding:8px 12px;border-radius:12px;background:rgba(15,20,17,.78);
  font-family:'Pretendard',sans-serif;backdrop-filter:blur(3px);
}
.lb-medals-head{font-size:11px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:rgba(247,247,245,.6);margin-bottom:4px;}
.lb-medal-row{display:flex;align-items:center;gap:8px;font-size:12.5px;line-height:1.75;}
.lb-medal-ico{flex:0 0 auto;font-size:14px;}
.lb-medal-name{color:rgba(247,247,245,.92);text-decoration:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.lb-medal-name:hover{text-decoration:underline;}
/* Times pushed to the right edge so the three line up as a column of
   numbers instead of drifting with the length of each name. */
.lb-medal-time{margin-left:auto;flex:0 0 auto;font-variant-numeric:tabular-nums;color:rgba(247,247,245,.7);}

/* While the original is on its way the <img> holds the 480px thumbnail (or
   nothing, for artworks from before thumbnails). A faint ground keeps that
   moment from looking like a broken image, and the thumbnail's softness is
   left alone — blurring it would make a correct picture look worse. */
.lightbox-overlay img.lb-img-loading,
#artworkStage img.lb-img-loading{background:rgba(247,247,245,.06);min-width:120px;min-height:120px;}

/* Lightbox edit dialog: the note under the "private" checkbox. */
.lb-edit-private-hint{font-size:12px;color:var(--muted);line-height:1.45;margin:4px 0 0 24px;}
