/* The structural set behind every page.
 *
 * THIS IS THE SOURCE. It lives in src/ and build.sh copies it to
 * assets/plot.css before build-assets.js runs.
 *
 * That indirection exists because build-assets.js content-hashes a stylesheet
 * and RENAMES it, so anything hand-appended to assets/plot.css after a build
 * writes to a filename that no longer exists - which silently creates a new
 * file that the next build then hashes AS the stylesheet, throwing away
 * everything that was in it. That happened twice. Nothing under assets/ is
 * hand-edited any more; edit this file.
 */

/* One drawing, fixed to the viewport, panned by scroll, so the site reads as a
   window travelling across a single sheet rather than a texture repeating
   behind each page. */
.plot{position:fixed;inset:0;z-index:0;pointer-events:none;overflow:hidden}
.plot__pan{position:absolute;top:0;left:0;width:100vw;will-change:transform;transform:translate3d(0,0,0)}
.plot__img{display:block;width:100%;height:auto;opacity:.5}
header,main,footer{position:relative;z-index:1}

/* Scroll cost. The sheet began as a 168vw layer of vector artwork: on a 1600px
   screen at 2x that is roughly forty megapixels the compositor had to keep
   rasterised while it moved, which is what made scrolling stutter. Main-thread
   work was never the problem - measured identical with the sheet present and
   with it removed - so the fix is on the raster side: a bitmap source instead
   of thousands of vector paths, and a layer the width of the viewport rather
   than nearly twice it. The pan is vertical now, which suits a scrolling page. */
@media (max-width:900px){
  .plot__pan{width:130vw;transform:none!important}
  .plot__img{opacity:.3}
}
@media (prefers-reduced-motion:reduce){
  .plot__pan{transform:none!important}
}

/* The old machinery this replaces: a different repeating texture per page, and
   the scattered 3D drawings in the margins. Both would only fight the sheet. */
body{background-image:none!important}
.sec--alt{background-image:none!important;background-color:rgba(23,29,37,.55)}
.secart{display:none!important}
/* .sec--art forced its drawing visible with a more specific !important rule
   than the blanket hide, and gave up a grid column to hold it. Hiding the
   drawing alone leaves the section reserving space for something not there. */
.sec--art > .secart{display:none!important}
.sec--art{display:block!important}
.sec--art > .wrap{max-width:1240px!important;margin:0 auto!important;min-width:0}

/* The logo. The badge is near square where the stripped monogram was a wide
   strip, so anything that sized it by width has to size it by height instead. */
.nav__brand .nav__mark{height:34px;width:auto}
.phero__mark{height:30px;width:auto}
footer .brandmark{height:96px;width:auto}

/* ---- the "see more" tile ------------------------------------------------- */
/* The gallery grid leaves an empty cell at the end of the last row. This fills
   it at the same size as a project tile, so the grid reads as complete instead
   of holed, and the link stops being a small orphan button underneath. Drawn
   as an open sheet rather than a photograph: hairline dashed edge, a plus at
   the centre, no image. */
.work__more{
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:14px;text-decoration:none;text-align:center;padding:28px;
  /* Same ground as a photo tile, or the sheet shows through and the cell
     reads as a hole rather than a card. The dashed edge is what says this one
     is an action and not a photograph. */
  border:1px dashed rgba(226,232,240,.34);
  background:var(--bg-card,#12161d);
  min-height:100%;
  transition:border-color .2s,background .2s,transform .2s cubic-bezier(.16,.84,.24,1);
}
.work__more:hover{
  border-color:var(--accent,#2f6be0);
  background:rgba(47,107,224,.07);
  transform:translateY(-2px);
}
/* the plus, drawn rather than typed, so it lines up exactly */
.work__more-mark{
  position:relative;width:46px;height:46px;flex:none;
  border:1px solid rgba(226,232,240,.4);border-radius:50%;
}
.work__more-mark::before,.work__more-mark::after{
  content:"";position:absolute;left:50%;top:50%;background:currentColor;
}
.work__more-mark::before{width:15px;height:1.5px;transform:translate(-50%,-50%)}
.work__more-mark::after{width:1.5px;height:15px;transform:translate(-50%,-50%)}
.work__more-label{
  font:700 17px/1.2 var(--body,inherit);letter-spacing:.02em;
  color:var(--ink,#e9edf2);
}
.work__more-sub{
  font:600 10.5px/1 ui-monospace,Menlo,monospace;letter-spacing:.16em;
  text-transform:uppercase;color:var(--ink-faint,rgba(226,232,240,.55));
}
.work__more:hover .work__more-label{color:var(--accent,#2f6be0)}
@media (max-width:700px){
  .work__more{padding:34px 20px;min-height:170px}
}

/* ---- the CTA watermark, removed ----------------------------------------- */
/* The CTA used the stripped monogram blown up behind the buttons. Swapping in
   the real badge did not work and could not: the badge carries its own blue
   box with a black outline, the CTA field is blue, so it read as a blue blob
   with a hard rounded-rect edge rather than a watermark. The old monogram got
   away with it because it was a bare letterform with no container - a logo
   with its own box is not a texture.
   The section does not need it. Strong blue field, two clear buttons. */
.cta::before{display:none!important}


/* ==========================================================================
   GLASS
   The liquid-glass look without backdrop-filter.
   Every liquid-glass component on 21st.dev is built on backdrop-filter: blur().
   On this site that is the single most expensive thing available: a fixed
   drawing sheet pans behind everything, so the backdrop changes on every scroll
   frame and each card would re-blur every frame. With ~20 cards that undoes the
   scroll fix outright.
   So the effect is built the way glass actually reads - edges and light, not
   blur. A specular glow where the light hits, a body tint that falls off, a
   bright hairline along the top edge, a dark one along the bottom, and a lift
   shadow. All of it is paint, none of it re-samples the backdrop, so it costs
   nothing on scroll.
   ========================================================================== */
:root{
  /* light striking the top-left of a pane */
  --glass-sheen:radial-gradient(135% 95% at 8% -18%,
    rgba(255,255,255,.15) 0%, rgba(255,255,255,.038) 40%, rgba(255,255,255,0) 72%);
  --glass-body:linear-gradient(180deg, rgba(36,45,59,.60) 0%, rgba(14,19,26,.74) 100%);
  /* the two hairlines are what sell it: lit top, shadowed bottom */
  --glass-edge:inset 0 1px 0 rgba(255,255,255,.18), inset 0 -1px 0 rgba(0,0,0,.4);
  --glass-lift:0 20px 44px -26px rgba(0,0,0,.95);
  --glass-line:rgba(255,255,255,.11);
  --glass-r:5px;
}

.work figure,
.work__more,
.svccard,
.glance,
.cvblock,
.lead,
.cols3 > div{
  background:var(--glass-sheen), var(--glass-body);
  border:1px solid var(--glass-line);
  border-radius:var(--glass-r);
  box-shadow:var(--glass-edge), var(--glass-lift);
}
/* .svccard draws its edge with outline rather than border, so the outline has
   to go or the card carries two rings. */
.svccard{outline:0}

/* The three-up blocks were bare text with no surface at all. They are panes now,
   which is what "boxes on the info" means. */
.cols3 > div{padding:26px 26px 28px}

/* Small panes: the chips and the keyed captions. Same language, less of it -
   a lift shadow on something this size reads as a mistake. */
.chips span,
.work figcaption{
  background:var(--glass-sheen), linear-gradient(180deg, rgba(36,45,59,.5), rgba(14,19,26,.6));
  border:1px solid var(--glass-line);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.13);
}
.chips span{border-radius:var(--glass-r)}
.work figcaption{border-width:1px 0 0}

/* Grid-gap lists. These fake their borders with a 1px gap over a background, so
   they get the sheen and the top hairline but no radius and no lift - rounding
   them would break the grid they sit in. */
.scope li,
.faq details{
  background:var(--glass-sheen), var(--glass-body);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.15);
}

/* Hover: the light moves and the pane rises. Transform and opacity only, so it
   stays on the compositor. */
.work figure,.svccard,.work__more{transition:transform .22s cubic-bezier(.16,.84,.24,1),box-shadow .22s,border-color .22s}
.work figure:hover,.svccard:hover,.work__more:hover{
  transform:translateY(-3px);
  border-color:rgba(255,255,255,.2);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.26), inset 0 -1px 0 rgba(0,0,0,.4),
             0 28px 56px -26px rgba(0,0,0,1);
}
@media (prefers-reduced-motion:reduce){
  .work figure:hover,.svccard:hover,.work__more:hover{transform:none}
}
/* The glass border rule overrode the dashed edge that marks the "see more" tile
   as an action rather than a photograph, so it read as an empty card. */
.work__more{border-style:dashed;border-color:rgba(255,255,255,.26)}
.work__more:hover{border-style:dashed}

/* A shade more light. At the first value the sheen was only legible on the
   large cards and vanished on the chips. */
:root{
  --glass-sheen:radial-gradient(135% 95% at 8% -18%,
    rgba(255,255,255,.19) 0%, rgba(255,255,255,.05) 40%, rgba(255,255,255,0) 74%);
}
/* .svcgrid used a 1px gap so the cards read as one strip divided by hairlines.
   Rounded glass panes butted together pinch at every shared corner, so the grid
   gets a real gap and the cards become separate panes - which is what the glass
   treatment is saying anyway. */
.svcgrid{gap:14px}

/* ==========================================================================
   PANES
   The reading blocks: the two story columns, the services block, the service
   areas block and the contact form. These had no surface at all.
   Heavier than the gallery and service cards on purpose - they carry the
   argument, so they should sit forward of the smaller cards rather than level
   with them. What makes them read as objects rather than tinted rectangles is
   the bevel and the two shadows: a real edge is lit on the top and left and
   shadowed on the bottom and right, and a real object casts both a tight
   contact shadow and a wide ambient one. A single soft shadow reads as a
   sticker; two read as something sitting above the page.
   Still no backdrop-filter, so still free on scroll.
   ========================================================================== */
.pane{
  position:relative;
  padding:clamp(26px,2.6vw,42px);
  border-radius:10px;
  background:
    /* broad specular where the light strikes */
    radial-gradient(120% 82% at 6% -12%,
      rgba(255,255,255,.20) 0%, rgba(255,255,255,.055) 34%, rgba(255,255,255,0) 66%),
    /* a tighter glint right on the corner, which is what stops it reading flat */
    radial-gradient(42% 32% at 2% -2%, rgba(255,255,255,.15), rgba(255,255,255,0) 72%),
    linear-gradient(168deg, rgba(46,58,76,.70) 0%, rgba(20,26,35,.78) 46%, rgba(11,15,20,.84) 100%);
  border:1px solid rgba(255,255,255,.13);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.30),             /* lit top edge */
    inset 1px 0 0 rgba(255,255,255,.09),             /* lit left edge */
    inset -1px 0 0 rgba(0,0,0,.55),                  /* shadowed right */
    inset 0 -1px 0 rgba(0,0,0,.7),                   /* shadowed bottom */
    inset 0 -44px 64px -48px rgba(122,166,255,.22),  /* bounce off the surface below */
    0 1px 2px rgba(0,0,0,.55),                       /* contact */
    0 30px 60px -30px rgba(0,0,0,1),                 /* lift */
    0 64px 104px -62px rgba(0,0,0,.9);               /* ambient */
}
.pane > :last-child{margin-bottom:0}

/* The photo beside a story column should match the pane it sits next to,
   otherwise one corner of the pair is round and the other is square. */
.split img{border-radius:10px}

@media (max-width:700px){
  .pane{padding:22px 20px 24px;border-radius:8px}
  .split img{border-radius:8px}
}
