/* ============================= WEB SHOWCASE =============================
   Page-specific styles layered on top of /assets/css/main.css's tokens
   (--base/--text/--accent/--s*/--gut/--hdh etc). This page has no cursor-
   light or the home page's continuous scroll-driven tick() loop, but .hd's
   own background/blur/border already read var(--hp,0) directly (main.css)
   — a small scroll listener at the bottom of this page just flips --hp
   between 0 and 1 on a fixed threshold instead of home's smoothly
   interpolated one, with a CSS transition here standing in for the
   per-frame easing home gets from tick(). Un-scrolled, --hp defaults to 0
   and the header is fully transparent, same as home. */
.web-hd{transition:background .3s ease,backdrop-filter .3s ease,border-color .3s ease}
.web-mark{font-size:17px;font-weight:600;letter-spacing:-.03em}

.web-hero{padding:var(--s8) var(--gut) var(--s6);max-width:720px}
.web-back{
  display:inline-block;margin-bottom:var(--s5);font-variation-settings:"MONO" 1;font-size:10.5px;
  letter-spacing:.13em;text-transform:uppercase;color:var(--muted);transition:color .25s;
}
.web-back:hover{color:var(--accent)}
.web-hero h1{margin:0 0 var(--s4);font-size:48px;font-weight:600;letter-spacing:-.02em}
.web-hero .sub{margin:0;max-width:56ch;color:var(--subtle);font-size:15px;line-height:1.6}

/* --- grid ----------------------------------------------------------------- */
.web-grid{
  display:grid;grid-template-columns:repeat(2,1fr);gap:var(--s7);
  padding:0 var(--gut) var(--s9);
}
.site-card{
  --pa:var(--accent);
  display:block;width:100%;text-align:left;border-radius:var(--r2);
  background:var(--surface);border:1px solid var(--edge);cursor:pointer;
  padding:var(--s6);
  transition:border-color .3s,background .3s,transform .3s cubic-bezier(.2,.85,.24,1);
}
.site-card:hover{
  border-color:color-mix(in srgb,var(--pa) 45%,var(--edge));
  background:color-mix(in srgb,var(--pa) 6%,var(--surface));
  transform:translateY(-2px);
}
.site-card:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
.site-card.is-hidden{visibility:hidden}

.card-meta{display:flex;flex-direction:column}
.card-meta .ed-kicker{margin-bottom:var(--s3)}
.card-meta h3{margin:0 0 6px;font-size:22px;font-weight:400;letter-spacing:-.014em;color:var(--text)}
.card-sub{margin:0 0 var(--s3);font-size:13px;color:var(--subtle);line-height:1.4}
.card-blurb{margin:0 0 var(--s4);font-size:12.5px;color:var(--muted);line-height:1.5;max-width:56ch}
.card-tags{display:flex;flex-wrap:wrap;gap:var(--s2)}
.card-tags .tool{font-size:9.5px}

@media (max-width:900px){
  .web-grid{grid-template-columns:1fr}
}
@media (max-width:520px){
  .web-hero{padding:var(--s7) var(--gut) var(--s5)}
  .web-hero h1{font-size:36px}
}

/* --- expand overlay --------------------------------------------------------
   the panel starts at final size/position; opening applies an inline
   transform mapping it back onto the clicked card's rect, then clears that
   transform on the next frame so the browser tweens it — a classic FLIP
   (see openSite() in web.js). The inner .expand-scale carries its own
   *separate* scale (real device width -> panel width) for the desktop/
   mobile viewport toggle, independent of the outer open/close animation. */
.expand-overlay{
  position:fixed;inset:0;z-index:90;display:flex;align-items:center;justify-content:center;
  padding:var(--s6);background:rgba(10,10,10,0);backdrop-filter:blur(0px);-webkit-backdrop-filter:blur(0px);
  transition:background .4s ease,backdrop-filter .4s ease;
}
.expand-overlay.on{background:rgba(10,10,10,.86);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px)}
.expand-overlay[hidden]{display:none}

/* fixed, simple panel dimensions — no more deriving panel/frame shape
   from the simulated device (aspect-ratio, container queries, all of
   it): that approach broke three separate times (a fixed-16:9-panel
   calibration that only matched one exact width; aspect-ratio fighting
   flex-shrink and tearing the frame's real ratio away from what the
   scale math assumed; cqw/cqh apparently not reflecting the frame's
   actual rendered size once it was itself sized by flex-grow inside a
   flex column). The frame column is now just an ordinary flex-fill
   element with no intrinsic sizing logic of its own, in both the
   desktop row layout and the mobile column one — see fitScale() in
   web.js, which measures its real rendered box with
   getBoundingClientRect() and sets the scale directly, sidestepping
   all of the above. */
.expand-panel{
  --vw:1280px;--vh:800px;
  position:relative;display:flex;flex-direction:row;width:min(1180px,100%);height:min(700px,86vh);
  background:var(--surface);border:1px solid var(--edge);border-radius:var(--r2);overflow:hidden;
  transform-origin:top left;will-change:transform;box-shadow:0 40px 100px rgba(0,0,0,.5);
}
.expand-panel.animating{transition:transform .55s cubic-bezier(.2,.85,.24,1)}

.expand-close{
  position:absolute;top:var(--s4);right:var(--s4);z-index:5;width:34px;height:34px;
  display:flex;align-items:center;justify-content:center;font-size:22px;line-height:1;
  color:#fff;background:rgba(15,15,15,.55);border-radius:100px;transition:background .25s;
}
.expand-close:hover{background:rgba(15,15,15,.85)}

.expand-frame-col{position:relative;flex:1;min-width:0;background:#0a0a0a}

/* lives in the details sidebar now (below the tech tags), not floating
   over the frame — accent matches the site's own ink (--pa, set on
   .expand-panel in openSite()) rather than the portfolio's generic
   --accent, same as the tags/kicker beside it */
.viewport-toggle{
  display:flex;gap:2px;margin-bottom:var(--s4);
  background:var(--base);border:1px solid var(--edge);border-radius:100px;padding:3px;
}
.viewport-toggle button{
  flex:1;display:flex;align-items:center;justify-content:center;gap:6px;padding:7px 0;border-radius:100px;
  font-variation-settings:"MONO" 1;font-size:10px;letter-spacing:.08em;text-transform:uppercase;
  color:var(--muted);transition:background .2s,color .2s;
}
.viewport-toggle button svg{width:14px;height:14px;flex:none}
.viewport-toggle button.on{background:var(--pa,var(--accent));color:#fff}
.viewport-toggle button:hover:not(.on){color:var(--text)}

/* .expand-scale is absolutely positioned, NOT a flex item, and that is the
   whole ballgame. As a flex item it carried the default flex-shrink:1, so
   the browser squashed it from width:var(--vw) (1280px) down to whatever
   the frame column happened to be *before* the transform ever ran — the
   iframe's internal viewport was therefore never actually 1280px wide (which
   is also why the embedded sites kept rendering their own tablet/mobile
   breakpoints), and fitScale() was then scaling an already-squashed box a
   second time. Every previous attempt here — the calibrated aspect ratio,
   aspect-ratio vs flex-shrink, cqw/cqh, the JS measurement — sat downstream
   of that and so could never work. An absolutely positioned box with an
   explicit width/height can't be resized by its parent's layout at all.
   Positioning/centring is done in fitScale()'s transform in real px, since
   flex or percentage centring would use the box's pre-transform size. */
.expand-frame-wrap{position:absolute;inset:0;overflow:hidden}
.expand-scale{
  position:absolute;top:0;left:0;
  width:var(--vw);height:var(--vh);transform-origin:top left;
  transition:transform .4s cubic-bezier(.2,.85,.24,1);
  box-shadow:0 0 0 1px rgba(232,230,225,.08);
  /* keeps the scaled surface on its own compositor layer between transforms
     too, not just while one is animating — without it the embedded site
     flashed un-painted white tiles as it scrolled */
  will-change:transform;backface-visibility:hidden;
}
.expand-scale iframe{display:block;width:100%;height:100%;border:0;background:#fff}

.expand-details{
  flex:0 0 300px;width:300px;max-height:100%;overflow-y:auto;padding:var(--s6) var(--s5);
  display:flex;flex-direction:column;border-right:1px solid var(--edge);
}
.ed-kicker{
  font-variation-settings:"MONO" 1;font-size:10px;letter-spacing:.13em;text-transform:uppercase;
  color:var(--pa,var(--accent));margin-bottom:var(--s3);
}
.expand-details h2{margin:0 0 var(--s3);font-size:26px;font-weight:400;letter-spacing:-.014em;color:var(--text)}
.ed-sub{margin:0 0 var(--s3);font-size:13.5px;color:var(--subtle);line-height:1.5}
.ed-blurb{margin:0 0 var(--s4);font-size:12.5px;color:var(--muted);line-height:1.5}
.expand-details .tools{margin-bottom:var(--s4)}
.expand-details .row-link{margin-top:auto;padding-top:var(--s4)}

@media (max-width:860px){
  .expand-overlay{padding:0}
  .expand-panel{flex-direction:column;width:100%;height:100%;border-radius:0}
  .expand-details{order:2;flex:0 0 auto;width:100%;max-height:40vh;border-right:0;border-top:1px solid var(--edge)}
  .expand-frame-col{order:1;flex:1;width:100%}
}
