/* ============================================================
   ELEV8DEPICTIONS · the /work wall
   ------------------------------------------------------------
   WHAT THIS IS
   The 36 portfolio cards on /work are marked up as .site-card /
   .site-chrome / .site-shot / .site-meta / .tag-*. Nothing in
   v3.css, in shell.css or in the section's own <style> block
   defines a single one of those class names, so every card fell
   to raw block flow: the fake address bar as a loose line of
   text, a full bleed screenshot, then the name, the sector and
   the tag stacked underneath it. This file is the missing half.

   WHY IT DID NOT ALREADY EXIST
   Not a deliberate omission, a drift. work.html shipped with the
   v2 site's card vocabulary (.site-card, styled in v2.css, which
   /work does not link) while the section's inline <style> block
   still styles the v3 vocabulary that tools/wall.py emits
   (.sun, .world, .w-shot). The two halves stopped matching and
   nobody noticed, because the page header, the legend and the
   closing note all kept rendering correctly. Only the cards went
   bare. If you are here because the wall looks wrong again,
   check that first: work.html's markup and its inline block are
   describing two different objects.

   SCOPE AND ORDER
   Everything is scoped under #work on purpose. It matches the
   voice of the section's own block, which is #work prefixed
   throughout, and it means this sheet does not care whether the
   <link> lands before or after v3.css. Nothing here leaks: the
   .site-* vocabulary appears nowhere else in the served site.

   TOKENS
   Only v3.css :root tokens. No new colour, no second radius
   scale, no fifth shadow recipe. The card is deliberately built
   as a sibling of #home-work .hw-card on the homepage: same
   16px radius, same panel, same hairline, same hover recipe, so
   a prospect moving from the homepage to /work feels no seam.
   ============================================================ */


/* ---------- the grid ----------
   Explicit column counts, never auto-fill. The wall's previous
   incarnation used minmax(min(100%,10rem),1fr), a 160px floor,
   which is how 36 builds ended up reading as a sheet of postage
   stamps. Measured at 390px, a two column card is 165px wide
   and its screenshot 103px tall, which puts a client's homepage
   headline at roughly six pixels. One column is the only
   readable phone layout, so the wall is simply long.
   Four columns tops out at 276px, narrower than the phone card,
   so three is the ceiling. Gap taken verbatim from .cs-wall on
   /case-studies, the nearest object on the site. */
/* The wrapper carries both .site-wall and #work-wall. Both are
   matched here on purpose: the id is the section JavaScript's
   handle and could reasonably be renamed with it, the class is
   the styling handle, and the wall should not go bare again
   because someone changed the other one. */
#work{--wall-gap:clamp(1.1rem,2.4vw,1.7rem)}
#work .site-wall,
#work-wall{
  display:grid;
  grid-template-columns:1fr;
  gap:var(--wall-gap);
  align-items:stretch;
}

@media (min-width:47.5rem){
  /* the site's own phone/tablet line. 335px per card here, within
     18px of the 353px phone card, so the step up is invisible */
  #work .site-wall,
  #work-wall{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media (min-width:66rem){
  /* the site's own wide line, shared with .c-cards and .c-proofcard */
  #work .site-wall,
  #work-wall{grid-template-columns:repeat(3,minmax(0,1fr))}
}

/* This sheet needs #work-wall to exist. When it was first written
   the 35 anchors were bare siblings of .work-head inside the
   wrap, with nothing to hang display:grid on, and there was a
   self cancelling :has() fallback here that promoted the wrap
   itself. The generator now emits the wrapper, so the fallback
   has gone rather than sitting here unreachable: dead rules for
   a shape the markup no longer has are exactly what broke this
   page in the first place. If the wall ever goes back to raw
   block flow, check for the wrapper before adding CSS. */


/* ---------- the card ----------
   One object, three bands, identical every time. That repetition
   is what makes 36 unlike businesses read as one wall.
   No resting shadow: with 36 cards a per card glow turns the
   whole page to mush, and every sibling object on the site
   (.hw-card, .c-proofcard, .world) rests flat and holds the glow
   back for hover.
   Flex column so a card in a short row still fills its row
   height, with the slack absorbed by .site-meta rather than
   opening a hole under the screenshot. */
#work .site-card{
  position:relative;
  display:flex;
  flex-direction:column;
  min-width:0;
  background:var(--panel-solid);
  border:1px solid var(--line);
  border-radius:16px;
  /* clip, not hidden, and the two lines are not interchangeable.
     overflow:hidden makes every card its own scroll container,
     and a view timeline resolves against its subject's nearest
     scrolling ancestor. With hidden, that ancestor is the card,
     which never scrolls, so the timeline is inactive, its range
     computes to 0% and the touch reveal further down this file
     silently never runs: 35 cards would look right and the four
     that matter would park a phone visitor on the client's OLD
     site for good. overflow:clip crops identically without
     creating a scroll container. hidden stays first as the
     fallback for engines with no clip support, which are the
     same engines with no scroll timelines, so the two
     degradations line up exactly. */
  overflow:hidden;
  overflow:clip;
  transition:transform .45s var(--ease-out),border-color .3s ease,box-shadow .45s var(--ease-out);
}

/* WHY: v3.css sets border-radius:4px on :focus-visible itself, so a
   keyboard user would square the corners off mid focus. This is
   1,2,0 against that rule's 0,1,0, so the card keeps its own
   corners no matter what order the sheets load in. The outline
   itself is v3.css's and is left alone. */
#work .site-card:focus-visible{border-radius:16px}

/* the house starlight pool, for the case where the generator has
   not put .starlit on the card. js/main.js only feeds --sx/--sy
   to .starlit elements, so this fallback is a fixed pool rather
   than a pointer tracked one, and it steps aside completely if
   .starlit is present so the two can never both paint.
   ::before, not ::after: #work .starlit::after is already claimed
   by the section's own block at 0,2,1. */
#work .site-card:not(.starlit)::before{
  content:"";
  position:absolute;
  inset:0;
  z-index:2;
  border-radius:inherit;
  pointer-events:none;
  opacity:0;
  background:radial-gradient(340px circle at 50% 0%,rgba(107,70,201,.16),transparent 70%);
  transition:opacity .35s ease;
}

/* ---------- hover ----------
   Wrapped in (hover:hover) the way .hw-card, .c-proofcard and
   .cs-card all are, so a touch device never gets stuck in a
   hover state after a tap.
   Amber is the default because that is the homepage recipe, so a
   browser with no :has() gets exactly what the homepage does.
   Demo and ask cards then step down to violet: not an apology,
   just the house body colour saying "mine, not theirs". */
@media (hover:hover){
  #work .site-card:hover{
    transform:translateY(-4px);
    border-color:rgba(255,152,24,.4);
    box-shadow:0 18px 44px -18px rgba(255,152,24,.28);
  }
  #work .site-card:not(.starlit):hover::before{opacity:1}
  #work .site-card:has(.tag-demo):hover,
  #work .site-card:has(.tag-ask):hover{
    border-color:var(--line-bright);
    box-shadow:0 18px 44px -18px rgba(107,70,201,.42);
  }
  /* the screenshot leans in. Held off the before and after cards
     on purpose: those four already have a moment, and scaling a
     clipped layer drifts the wipe seam off the visual boundary. */
  #work .site-card:hover .site-shot:not(.has-before) img{transform:scale(1.03)}
  #work .site-card:hover .archive-shot .ask-line{color:var(--star-dim)}
}

/* Keyboard focus gets the same lit state, outside the hover query
   so it still works on a laptop with a touchscreen. No lift:
   sliding an element out from under its own focus ring is
   disorienting, and the border and glow already say "this one". */
#work .site-card:focus-visible{
  border-color:rgba(255,152,24,.4);
  box-shadow:0 18px 44px -18px rgba(255,152,24,.28);
}
#work .site-card:has(.tag-demo):focus-visible,
#work .site-card:has(.tag-ask):focus-visible{
  border-color:var(--line-bright);
  box-shadow:0 18px 44px -18px rgba(107,70,201,.42);
}
#work .site-card:not(.starlit):focus-visible::before{opacity:1}

/* press, matching .btn:active */
#work .site-card:active{transform:translateY(-1px) scale(.995)}


/* ---------- the chrome strip ----------
   Violet dots, not red, amber and green. Traffic lights are a
   foreign operating system's motif and appear nowhere else on
   this site; .hw-dots on the homepage already settled this.
   gap is 4px so the three dots read as one run, and .url takes
   its own margin to open the space before the address. */
#work .site-chrome{
  flex:none;
  display:flex;
  align-items:center;
  gap:4px;
  padding:.5rem .8rem;
  background:var(--indigo);
  border-bottom:1px solid var(--line);
}
#work .site-chrome i{
  flex:0 0 auto;
  width:7px;
  height:7px;
  border-radius:50%;
  background:var(--violet-3);
}
/* The address line is the honesty device on this wall: a real
   domain means a real business is running on it, and "slug ·
   demo" says plainly that this one is on my hosting. It does
   more work than any label, so it must never be truncated into
   nonsense: min-width:0 lets flex actually shrink it, and the
   ellipsis takes it from there. 6.4:1 on --indigo. */
#work .site-chrome .url{
  min-width:0;
  margin-left:.45rem;
  font-size:.82rem;
  line-height:1.5;
  letter-spacing:.01em;
  color:var(--star-faint);
  white-space:nowrap;
  overflow:hidden;
  text-overflow:ellipsis;
}


/* ---------- the screenshot ----------
   aspect-ratio is the whole layout reservation. None of the 31
   wall images carry width or height attributes, so without this
   36 lazy loaded pictures would reflow the page under the
   reader's thumb as they arrive. Every existing shot measures
   exactly 16:10 (1000x625 or 2040x1275, no outliers), so cover
   crops nothing off any of them.
   The background is the placeholder colour while an image is
   still in flight, so there is no white flash on a dark page. */
#work .site-shot{
  flex:none;
  position:relative;
  overflow:hidden;
  aspect-ratio:16/10;
  background:var(--space-2);
}
#work .site-shot img{
  width:100%;
  height:100%;
  object-fit:cover;
  object-position:top center;
  transition:transform .8s var(--ease-out);
}

/* ---------- archived builds ----------
   Five cards have no screenshot because the build is offline and
   no capture survives. They get an honest designed panel with
   the initials on it, never an invented or reconstructed image.
   Carried over from v2.css and recoloured onto v3 tokens. */
#work .archive-shot{
  display:flex;
  flex-direction:column;
  align-items:center;
  justify-content:center;
  gap:.5rem;
  text-align:center;
  background:radial-gradient(90% 120% at 50% 0%,rgba(240,168,50,.10),transparent 60%),var(--space-2);
}
#work .archive-shot .mono{
  font-size:clamp(2.2rem,7vw,3rem);
  font-weight:800;
  letter-spacing:-.02em;
  line-height:1;
  color:var(--amber);
}
#work .archive-shot .ask-line{
  font-size:.85rem;
  font-weight:560;
  line-height:1.4;
  color:var(--star-faint);
  padding-inline:1rem;
  transition:color .3s ease;
}


/* ---------- the meta row ----------
   flex:1 so a card that is short for its row grows here rather
   than leaving a gap under the screenshot, and align-items
   centres the contents in whatever height it ends up with. */
#work .site-meta{
  flex:1 1 auto;
  display:flex;
  align-items:center;
  gap:.6rem;
  min-width:0;
  padding:.85rem 1rem 1rem;
}
#work .site-meta > div{min-width:0}
#work .site-meta h3{
  font-size:1.02rem;
  font-weight:640;
  line-height:1.3;
  letter-spacing:-.015em;
  color:var(--star);
}
#work .site-meta p{
  margin-top:.12rem;
  font-size:.9rem;
  line-height:1.45;
  color:var(--star-faint);
}


/* ---------- the three tags ----------
   These have to reproduce the legend directly above the wall
   exactly, mark for mark, or the key is a lie about the cards.
   The legend's rules (#work .w-tag, .t-live, .t-demo, .t-ask)
   live in the section's own block at 0,2,0 and cannot be reached
   from here, so the geometry is repeated rather than shared.
   Sentence case, no tracking: v2.css set these in tracked
   uppercase, which is the treatment the house rules ban.
   9.8:1 on --panel-solid. */
#work .tag-live,
#work .tag-demo,
#work .tag-ask{
  flex:0 0 auto;
  margin-left:auto;
  display:inline-flex;
  align-items:center;
  gap:.45rem;
  font-size:.85rem;
  font-weight:560;
  line-height:1.4;
  white-space:nowrap;
  color:var(--star-dim);
}
#work .tag-live::before,
#work .tag-demo::before,
#work .tag-ask::before{
  content:"";
  flex:none;
  width:7px;
  height:7px;
  border-radius:50%;
}
/* live: on the client's own domain, a real business running on
   it. Amber is the brand's "this is the permanent thing" signal,
   and the pulse is the only motion in a resting card. */
#work .tag-live::before{
  background:var(--amber);
  animation:wall-live-pulse 2.6s ease-in-out infinite;
}
/* demo: a complete working site on my hosting, live right now */
#work .tag-demo::before{
  background:var(--violet-glow);
  box-shadow:0 0 9px 1px rgba(107,70,201,.5);
}
/* ask: offline, nothing to click through to, so a hollow mark for
   an absent thing and deliberately the quietest of the three */
#work .tag-ask::before{
  background:transparent;
  border:1px solid var(--line-bright);
}
/* WHY a private copy of the legend's work-pulse: that keyframe is
   declared in work.html's inline block alongside about ninety
   lines of dead .sun and .world rules that are queued for
   deletion. Borrowing it would make this sheet fail silently the
   day that block goes. Same values, same phase. */
@keyframes wall-live-pulse{
  0%,100%{box-shadow:0 0 5px 0 rgba(240,168,50,.4)}
  50%{box-shadow:0 0 12px 2px rgba(240,168,50,.75)}
}


/* ============================================================
   BEFORE AND AFTER, on the four builds that genuinely have one
   ------------------------------------------------------------
   j-davey-guitars, panda-monium-playtime, telford-bathrooms and
   telford-plumbers are the only four with a real captured
   photograph of the client's old site. Every other card on this
   wall has none and must never imply one, so nothing in this
   block can be reached without .has-before on the shot.

   THE MECHANIC: a hard edged wipe, left to right.
   The BEFORE sits on top and is clipped away, exposing the AFTER
   which is the permanent base layer underneath. An ember
   hairline rides the clip boundary and rests down the left edge,
   which reads as a handle without a word of instruction.

   WHY A WIPE AND NOT A DRAGGABLE SLIDER: a slider needs a
   handle, a handle is an interactive control, and the whole card
   is already a single <a>. Nesting a control inside it breaks
   the link and the keyboard order both. A slider's entire
   premise is also resting mid state, which the brief rules out.

   WHY NOT A RESTING 50/50 SPLIT: at 353px a split shows 176px of
   each, and half of a 1440px wide homepage capture is not a
   picture of anything. The brief also asks for the before at
   rest, whole.

   TWO STABLE STATES ONLY, both complete pictures: fully before,
   or fully after. There is no resting mid state anywhere. */

#work .site-shot.has-before{
  isolation:isolate;
  view-timeline-name:--wall-ba;
  view-timeline-axis:block;
}
#work .site-shot.has-before img{
  position:absolute;
  inset:0;
  width:100%;
  height:100%;
  object-fit:cover;
  object-position:top center;
}
#work .shot-after{z-index:1}

/* The before is the TOP layer, and that is what makes a missing
   file degrade honestly with no script involved: a broken <img>
   paints nothing but its alt string, so a transparent background
   and zeroed text leave the finished build showing underneath
   and the card quietly becomes an ordinary one. Never give this
   layer a solid background, or a 404 paints a slab over a
   perfectly good after. */
#work .shot-before{
  z-index:2;
  background:transparent;
  font-size:0;
  color:transparent;
  clip-path:inset(0 0 0 0);
  transition:clip-path .75s var(--ease-out);
}

/* the seam. Drawn on the shot rather than on the clipped layer,
   because clip-path clips borders and shadows too. left is
   animated directly: an unregistered custom property does not
   interpolate and the wipe would jump. The fade is delayed so
   the line stays with the boundary and only goes as it arrives. */
#work .site-shot.has-before::after{
  content:"";
  position:absolute;
  top:0;
  bottom:0;
  left:0;
  width:2px;
  z-index:3;
  pointer-events:none;
  background:linear-gradient(180deg,rgba(255,152,24,0),var(--ember) 14%,var(--ember) 86%,rgba(255,152,24,0));
  transition:left .75s var(--ease-out),opacity .3s ease .4s;
}

/* A span inside an <a>, so it can never become a tap target of
   its own. 9.3:1 even over a solid white screenshot, which is
   the worst case on this wall. It clears the seam by .7rem.
   The generator must give it aria-hidden="true": its text
   otherwise joins the link's accessible name and repeats what
   the before image's alt already says. */
#work .ba-badge{
  position:absolute;
  top:.5rem;
  left:.7rem;
  z-index:4;
  pointer-events:none;
  padding:.14rem .6rem;
  font-size:.78rem;
  font-weight:560;
  line-height:1.5;
  color:var(--star);
  background:rgba(7,3,15,.78);
  border:1px solid var(--line-bright);
  border-radius:999px;
  transition:opacity .3s ease,transform .45s var(--ease-out);
}

/* revealed: hover, keyboard focus, and press.
   :active is what makes this work on touch without hijacking
   anything. It fires on touch down, so a held finger gets a
   genuine peek at the finished build, and the link still
   navigates on release exactly like the other 32 cards. */


/* ---------- touch: the wipe runs off the scroll ----------
   A phone has no hover, and a card that rests on the before for
   good would leave a prospect looking at a client's old site
   with this studio's name on the page. So on a coarse pointer
   the wipe is driven by the card's own arrival on screen.
   One named timeline shared by all three animations, taken from
   the shot itself: view(block) on each element separately would
   give the badge its own progress and let it drift out of step
   with the wipe it belongs to.
   The range closes at entry 70%, so by the time a card is fully
   on screen it has always settled, whole, on the after. */
/* THE SCROLL DRIVEN WIPE IS GONE, and this is the note explaining why so that
   nobody clever puts it back.

   It drove the before/after off the card's own arrival on screen with
   animation-timeline:view(). It looked wonderful in a continuous scroll and it
   failed three ways the moment anybody actually used the page:

   1. A HALF STATE WAS REACHABLE AND STABLE. Stop scrolling with a card 43 to
      77 percent off the bottom and it rests at, measured, clip-path
      inset(0 0 0 32%): the left third the new site, the right two thirds the
      old one, a 2px ember seam down the middle and the badge a ghost at 0.11
      opacity that cannot be read. It does not look like a reveal. It looks
      like a rendering bug on the page whose whole job is proving this person
      can build.
   2. IT KILLED :active. A running animation's output outranks a declaration in
      the cascade, so :active .shot-before never applied while the timeline
      branch was live. Once a card had settled, a phone had no route back to
      the before at all.
   3. A REDUCED MOTION PHONE PARKED ON THE CLIENT'S OLD SITE, permanently, with
      a 12.5px badge as the only explanation. A portfolio resting on the work
      we replaced is the exact opposite of the point.

   What replaces it is the branch that was already written underneath as the
   no-timeline fallback, whose own comment had the right instinct: rest on the
   finished build, reveal the before on hover or on press and hold. Applied
   everywhere now, on one axis, with no half way. */

/* REST ON THE AFTER. EVERY DEVICE, EVERY MOTION SETTING, NO EXCEPTIONS.
   The wall exists to show what was built, so the finished site is what a card
   is at rest and the old one is what you go and look for. The reveal is the
   same gesture everywhere: hover with a pointer, press and hold without one,
   and focus for a keyboard. */
#work .shot-before{clip-path:inset(0 0 0 100%)}
#work .site-shot.has-before::after{left:100%;opacity:0}

#work .site-card:active .shot-before,
#work .site-card:focus-visible .shot-before{clip-path:inset(0 0 0 0)}
#work .site-card:active .site-shot.has-before::after,
#work .site-card:focus-visible .site-shot.has-before::after{left:0;opacity:1}
@media (hover:hover){
  #work .site-card:hover .shot-before{clip-path:inset(0 0 0 0)}
  #work .site-card:hover .site-shot.has-before::after{left:0;opacity:1}
}


/* ---------- reduced motion ----------
   v3.css already flattens durations globally, but it does not
   remove a transform and it cannot reach a scroll driven
   animation, so both are cancelled here by hand. The wipe
   becomes a plain swap: the information is never withheld, only
   the movement is. */
@media (prefers-reduced-motion:reduce){
  #work .site-card,
  #work .site-card:hover,
  #work .site-card:active{transform:none}
  #work .site-shot img,
  #work .site-card:hover .site-shot img{transform:none}
  #work .tag-live::before{animation:none}

  #work .site-shot.has-before .shot-before,
  #work .site-shot.has-before::after,
  #work .site-shot.has-before .ba-badge{animation:none}
  #work .site-shot.has-before::after{display:none}

  /* Same rest state as everywhere else, just crossed rather than wiped: a
     clip-path travelling across the card is motion, and somebody who has asked
     for less of it still deserves to see the before. What must NOT happen is
     the old behaviour, where reduced motion flipped the resting state and left
     a phone parked on the client's old website for good. */
  #work .shot-before{clip-path:none;opacity:0;transition:opacity .2s linear}
  #work .site-card:hover .shot-before,
  #work .site-card:focus-visible .shot-before,
  #work .site-card:active .shot-before{clip-path:none;opacity:1}
}

/* THE WHATSAPP BUTTON WAS SITTING ON THE TAG.
   It is fixed at the bottom right of the viewport and every card put Live or
   Demo at the bottom right of its meta row, so at some scroll positions the
   button covered the single mark that tells a prospect whether this is a real
   client site or one of ours. Measured at 390px: button x 327 to 377, tag
   x 305 to 355.

   Below the phone breakpoint the meta row stacks and the tag sits under the
   title, hard left, where nothing floats over it. Above it, there is room to
   the right of the button and the row stays as it was. */
@media (max-width:47.5rem){
  #work .site-meta{flex-direction:column; align-items:flex-start; gap:.55rem}
  #work .site-meta > div{width:100%}
  #work .site-meta .tag-live,
  #work .site-meta .tag-demo,
  #work .site-meta .tag-ask{margin-left:0}
}
