/* Public pages — the ONE stylesheet shared by / (home) and /portfolio.
   These pages are standalone: they never load style.css (the internal design system)
   and never extend base.html. The tokens below were portfolio.html's own palette,
   lifted out on 2026-09-16 so the two public pages share one look and one modal.
   Page-specific rules (the portfolio grid, the home hero) stay in their templates. */

* { box-sizing: border-box; margin: 0; }
:root { --bg:#101114; --card:#1a1c20; --card2:#232529; --stroke:#2c2f35;
        --text:#f2f3f5; --dim:#9aa0a8; --accent:#3b6dff; }
body { background:var(--bg); color:var(--text);
       font:15px/1.5 "Builder Sans","Segoe UI",system-ui,sans-serif; }

/* ── 3D modal (driven by /static/viewer/public-3d.js) ── */
.ov { position:fixed; inset:0; background:rgba(8,9,11,.82); display:none;
      align-items:center; justify-content:center; z-index:50; }
.ov.open { display:flex; }
.box { background:var(--card); border:1px solid var(--stroke); border-radius:16px;
       width:min(560px, 94vw); overflow:hidden; }
.box .bar { display:flex; align-items:center; gap:10px; padding:14px 18px; }
.box .bar .nm { font-weight:700; flex:1; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.box .bar button { background:none; border:0; color:var(--dim); font-size:20px; cursor:pointer; }
.box canvas { width:100%; aspect-ratio:1; display:block; background:radial-gradient(circle at 50% 35%, #26282d, #141518); }
.box .sw { display:flex; flex-wrap:wrap; gap:6px; padding:12px 18px 0; }
.box .sw button { background:var(--card2); border:1px solid var(--stroke); color:var(--dim);
                  border-radius:999px; padding:4px 12px; font-size:12px; font-weight:600; cursor:pointer; }
.box .sw button.on { border-color:var(--accent); color:var(--text); }
.box .foot { display:flex; align-items:center; justify-content:space-between; padding:12px 18px 16px; }
.box .hint { color:var(--dim); font-size:12px; }
.box .foot a { color:var(--accent-fg, #fff); background:var(--accent); border-radius:8px; padding:8px 16px;
               font-size:13px; font-weight:700; text-decoration:none; }
/* loading state. The renderer keeps the last frame on the canvas (preserveDrawingBuffer),
   so opening item B used to show item A under a dim "loading 3D…" until B rendered —
   "it will show the previous item" (member report 2026-09-11). `.busy` hides the stale
   canvas and shows a spinner; `.err` shows text only. */
.stage { position:relative; display:flex; align-items:center; justify-content:center; }
.spin { display:none; position:absolute; align-items:center; gap:10px;
        color:var(--dim); font-size:13px; }
.stage.busy .spin, .stage.err .spin { display:flex; }
.stage.busy canvas, .stage.err canvas { visibility:hidden; }
.stage.err .ring { display:none; }
.ring { width:18px; height:18px; border-radius:50%; flex:none;
        border:2px solid var(--stroke); border-top-color:var(--accent);
        animation:spin .8s linear infinite; }
@keyframes spin { to { transform:rotate(360deg); } }
/* a colour swap keeps the old colour on screen (seamless) and marks the swatch busy */
.box .sw button.busy { position:relative; padding-left:26px; }
.box .sw button.busy::before { content:""; position:absolute; left:9px; top:50%; margin-top:-6px;
        width:12px; height:12px; border-radius:50%; border:2px solid var(--stroke);
        border-top-color:var(--accent); animation:spin .8s linear infinite; }
@media (prefers-reduced-motion: reduce) {
  .ring, .box .sw button.busy::before { animation:none; border-top-color:var(--stroke); }
}
/* short screens (incl. landscape phones / small laptops): the 560px modal with a
   square canvas is taller than the viewport — shrink it so it always fits */
@media (max-height: 820px) {
  .box { width:min(440px, 94vw); }
}
.box { max-height:94vh; overflow-y:auto; }
/* phones: bigger touch targets in the modal */
@media (max-width: 560px) {
  .box .sw { padding:10px 12px 0; }
  .box .sw button { padding:7px 14px; font-size:13px; }
  .box .bar { padding:12px 14px; }
  .box .foot { padding:12px 14px 14px; justify-content:flex-end; }
  .box .hint { display:none; }   /* "scroll to zoom" isn't a thing on touch */
}
