/* The work gallery, and the thermal overlay the coverage map wears.
 
 One sheet for two pages, the way assets/reviews.css is one sheet for forty-two:
 /our-work/ draws the full gallery, the homepage draws three cards of it plus the
 heat over the ring, and both want the same card, the same lightbox and the same
 legend. A page that carries none of the markup pays nothing for linking it.
 
 Loaded after assets/reviews.css and before whatever a page says for itself.
 Every selector here is a .work-* or a .heat-*, so where exactly it sits relative
 to assets/area.css does not matter -- there is nothing for the two to disagree
 about.
 
 It inherits the site's tokens rather than introducing any: the gallery is the
 same navy, ice and gold as the rest of the page and moves with the time of day
 like everything else. The one exception is the heatmap's own palette, which is
 fixed for the same reason the heatwave chart's is (see --heat-warm in
 chrome.css): it is a reading, not decoration, and a spring afternoon must not
 be able to recolour where we have been busy. */

/* ---------- THE BAND ---------- */
.work-head{
  display:flex;align-items:flex-end;justify-content:space-between;
  gap:32px;flex-wrap:wrap;margin-bottom:28px;
}
.work-head .section-head{margin-bottom:0;}
.work-count{
  font-family:'JetBrains Mono',monospace;font-size:0.78rem;letter-spacing:0.1em;
  text-transform:uppercase;color:var(--stone);font-weight:600;
  white-space:nowrap;padding-bottom:6px;
}

/* The location filter. The same chip the town list uses, so a reader who has
   already met these names on the coverage map meets them again in the shape
   they met them in -- but a button rather than a link, because this one narrows
   the page instead of leaving it. Only towns that actually have work on the
   page get a chip: an empty filter is a dead end dressed as a choice. */
.work-filter{
  display:flex;flex-direction:column;gap:14px;margin-bottom:34px;
  scroll-margin-top:96px;
}
/* One row per kind of filter -- town, premises, unit type -- each labelled, since
   "Commercial" and "Ducted" sitting in one undifferentiated rail would read as
   one list of places. */
.work-filter-group{display:flex;flex-wrap:wrap;gap:10px;align-items:center;}
.work-filter-label{
  font-family:'JetBrains Mono',monospace;font-size:0.72rem;letter-spacing:0.1em;
  text-transform:uppercase;color:var(--stone);font-weight:600;min-width:92px;
}
/* A flex container ignores the hidden attribute, so the bar has to be told: it
   is served hidden and only comes out when there are two towns to choose
   between, and without this a page with one town's work carries 34px of empty
   chip rail. Same for the plates below, where it is the filter itself that
   stops working. */
.work-filter[hidden]{display:none;}
.work-filter button{
  font:inherit;cursor:pointer;
  background:var(--ice-soft);border:1px solid var(--line);color:var(--ink-soft);
  font-size:0.88rem;font-weight:600;padding:8px 14px;border-radius:999px;
  transition:background .18s ease,border-color .18s ease,color .18s ease,transform .18s ease;
}
.work-filter button:hover{background:var(--white);border-color:var(--sky-deep);transform:translateY(-1px);}
.work-filter button[aria-pressed="true"]{
  background:var(--navy);border-color:var(--navy);color:var(--trust-ink);
}
.work-filter button span{
  font-family:'JetBrains Mono',monospace;font-size:0.78rem;opacity:0.7;margin-left:6px;
}
/* Nothing left behind this chip with the other filters as they are. Still
   pressable -- it may be the choice the reader wants to switch to -- just quieter. */
.work-filter button.is-empty:not([aria-pressed="true"]){opacity:0.5;}

/* ---------- ONE JOB ---------- */
/* A plate rather than a card in a grid of three: a job is a few photographs and
   a paragraph about who it was for, and three of those side by side would crop
   both halves to nothing. So each one runs the width of the page, photographs on
   one side and the writing on the other, and the sides swap on every second job
   so a page of them reads as a column of work instead of a table.
 
   The writing takes the wider side of the two. Nothing in a plate sets its
   height except the paragraph in it, and a paragraph of a hundred words in half
   the page is eleven lines where the same words across three fifths of it are
   eight -- so the plate came to something like a screenful each, and the
   photograph panel, stretched to match, turned a landscape photograph into a
   tall narrow slice of itself. Widening the measure is the whole of the fix:
   fewer lines, a shorter plate, and a panel the right way round for the
   pictures in it. */
.work-list{display:flex;flex-direction:column;gap:22px;scroll-margin-top:96px;}
.work-none[hidden]{display:none;}

/* Five plates a page, and the buttons between the pages under the last of them.
   Same chip as the filters, so the two read as controls on one list. */
.work-pager{
  display:flex;flex-wrap:wrap;justify-content:center;align-items:center;gap:8px;
  margin-top:30px;
}
.work-pager[hidden]{display:none;}
.work-pager button{
  font:inherit;cursor:pointer;min-width:42px;
  background:var(--ice-soft);border:1px solid var(--line);color:var(--ink-soft);
  font-size:0.88rem;font-weight:600;padding:8px 14px;border-radius:999px;
  transition:background .18s ease,border-color .18s ease,color .18s ease;
}
.work-pager button:hover:not(:disabled){background:var(--white);border-color:var(--sky-deep);}
.work-pager button[aria-current="page"]{
  background:var(--navy);border-color:var(--navy);color:var(--trust-ink);
}
.work-pager button:disabled{opacity:0.45;cursor:default;}
.work-job{
  background:var(--white);border:1px solid var(--line);border-radius:var(--radius);
  overflow:hidden;
  display:grid;grid-template-columns:minmax(0,0.88fr) minmax(0,1.12fr);
  box-shadow:0 10px 26px rgba(11,42,74,0.06);
}
/* Sides swap on every second plate, counted over the plates you can actually
   see: filtered to one town, plain nth-child would go on counting the hidden
   ones and leave two visible jobs in a row photographs-right.
 
   The track widths swap with them. Ordering alone moves the panel into the other
   column and leaves the widths where they were, which handed every second plate
   its writing in the narrow track -- the one thing this layout is trying not to
   do. */
.work-job:nth-child(even of :not([hidden])){
  grid-template-columns:minmax(0,1.12fr) minmax(0,0.88fr);
}
.work-job:nth-child(even of :not([hidden])) .work-shots{order:2;}
.work-job[hidden]{display:none;}
.work-body{padding:26px 30px;display:flex;flex-direction:column;}
.work-meta{
  display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  font-family:'JetBrains Mono',monospace;font-size:0.76rem;letter-spacing:0.1em;
  text-transform:uppercase;font-weight:600;margin-bottom:10px;
}
.work-meta .work-place{color:var(--ink);}
.work-meta .dot{color:var(--line);}
.work-meta time{color:var(--stone);}
.work-job h3{font-size:1.32rem;line-height:1.2;}
.work-text{color:var(--ink-soft);font-size:1rem;margin:12px 0 0;}
.work-text p{margin:0 0 12px;}
.work-text p:last-child{margin-bottom:0;}

/* The sample mark. These jobs ship with the site so the gallery and the heatmap
   have something to be on day one, and saying so on the card is the only honest
   way to have them there: the photographs are stock and the work is not ours.
   Editing one in /workadmin/ clears the flag, and the mark with it. */
.work-tag{
  display:inline-flex;align-items:center;gap:6px;
  background:var(--ice);color:var(--ink-soft);
  border-radius:999px;padding:3px 10px;
  font-family:'JetBrains Mono',monospace;font-size:0.68rem;letter-spacing:0.08em;
  text-transform:uppercase;font-weight:600;
}

/* The linked Google review, where the job has one. Deliberately a quotation and
   not a testimonial: the words, the name, the score and a link to the review on
   Google, which is the same rule the reviews band follows -- a quote nobody can
   check is worth very little. */
.work-review{
  margin-top:auto;padding-top:18px;
  border-top:1px dashed var(--line);
}
.work-review blockquote{
  margin:10px 0 0;color:var(--ink);font-size:0.96rem;line-height:1.5;
}
.work-review blockquote::before{content:'\201C';}
.work-review blockquote::after{content:'\201D';}
.work-review-who{
  margin:10px 0 0;font-size:0.85rem;color:var(--stone);
  display:flex;align-items:center;gap:8px;flex-wrap:wrap;
}
.work-review-who b{color:var(--ink);font-size:0.9rem;}
.work-review-who a{color:var(--sky-deep);font-weight:700;text-decoration:none;}
.work-review-who a:hover{text-decoration:underline;}
.work-review-who svg{width:14px;height:14px;flex-shrink:0;}

/* ---------- THE PHOTOGRAPHS ---------- */
/* One photograph leads and the rest are a rail along the bottom of the panel.
 
   The rail used to be a column of tiles down the right of it, which took a third
   of the panel's width off the lead and then gave it the full height of the
   writing beside it -- a portrait crop of a landscape photograph, every time. Put
   the tiles underneath instead and the lead keeps the whole width and gives up
   only the rail's 88px of height, so it comes out landscape on a plate of any
   ordinary height: a job with one photograph and a paragraph of writing lands
   within a few per cent of three-to-two, and a wordier one widens rather than
   narrowing.
 
   Three cases, because a rail of one tile is a letterbox and a hole in a grid is
   worse than no grid: one photograph fills the panel, two split it into bands,
   three or more are a lead and a rail. Only two of them need saying -- a lone
   tile grows into the panel on its own, which is what .one is left carrying no
   rule of its own for. Heights are all flex shares rather than grid rows, so
   whichever side of the plate is taller the pictures divide it between them and
   nothing has to be told a pixel figure. */
.work-shots{
  display:flex;flex-direction:column;gap:3px;background:var(--ice);
  min-height:280px;
}
/* The lead takes the panel less the rail. min-height is the floor for a job
   whose writing is a line or two -- the panel's own min-height covers the rest. */
.work-shots > .work-shot{flex:1 1 auto;min-height:0;}
.work-shots > .work-shot:first-child{min-height:190px;}
/* Two photographs: two bands, the lead a little the taller of the pair. Only a
   little -- at one and a half and up the second one comes out a sliver on a
   plate this short, and two photographs of a job are usually two halves of the
   same story rather than a picture and a footnote. */
.work-shots.two > .work-shot:first-child{flex:1.15 1 0;min-height:150px;}
.work-shots.two > .work-shot + .work-shot{flex:1 1 0;min-height:118px;}
/* The rail. A fixed height because it is a row of thumbnails and not a second
   look at the job: it should not grow when the writing beside it runs long. */
.work-rail{display:flex;gap:3px;flex:0 0 88px;}
.work-rail .work-shot{flex:1 1 0;min-width:0;}
.work-shot{
  position:relative;padding:0;border:0;margin:0;cursor:zoom-in;
  background:var(--ice-soft);overflow:hidden;min-height:0;
}
/* The figure fills its button absolutely, which keeps the button's height purely
   a matter of its flex share -- a percentage height on an in-flow child would
   have to resolve against a height the flex line is still working out. */
.work-shot figure{position:absolute;inset:0;margin:0;}
.work-shot img{
  width:100%;height:100%;object-fit:cover;display:block;
  transition:transform .4s ease;
}
.work-shot:hover img{transform:scale(1.04);}
.work-shot:focus-visible{outline:3px solid var(--gold);outline-offset:-3px;}
/* The caption, folded away until the photograph is hovered or focused. It is
   optional per photograph and most will not have one, so it cannot be allowed to
   take up room in the grid whether or not it says anything. */
.work-shot figcaption{
  position:absolute;left:0;right:0;bottom:0;
  padding:22px 12px 9px;
  background:linear-gradient(to top, rgba(7,29,51,0.82), rgba(7,29,51,0));
  color:#fff;font-size:0.78rem;line-height:1.35;text-align:left;
  opacity:0;transform:translateY(6px);transition:opacity .2s ease,transform .2s ease;
}
.work-shot:hover figcaption,.work-shot:focus-visible figcaption{opacity:1;transform:none;}
.work-more{
  position:absolute;inset:0;display:grid;place-items:center;
  background:rgba(11,42,74,0.55);color:#fff;
  font-family:'JetBrains Mono',monospace;font-weight:600;font-size:1rem;
}

/* ---------- THE LIGHTBOX ---------- */
.work-lightbox{
  position:fixed;inset:0;z-index:200;
  display:none;place-items:center;
  background:rgba(7,29,51,0.93);
  padding:24px;
}
.work-lightbox[open],.work-lightbox.show{display:grid;}
.work-lightbox figure{margin:0;max-width:min(1100px,100%);max-height:100%;display:flex;flex-direction:column;gap:14px;}
.work-lightbox img{
  max-width:100%;max-height:78vh;width:auto;height:auto;margin:0 auto;
  border-radius:8px;background:#0B2A4A;
}
.work-lightbox figcaption{
  color:var(--ice-soft, #DCEEF5);font-size:0.95rem;text-align:center;
  display:flex;flex-direction:column;gap:6px;
}
.work-lightbox figcaption b{color:#fff;font-weight:600;}
.work-lightbox figcaption span{
  font-family:'JetBrains Mono',monospace;font-size:0.76rem;
  letter-spacing:0.08em;text-transform:uppercase;opacity:0.7;
}
.work-lb-btn{
  position:absolute;top:50%;transform:translateY(-50%);
  width:52px;height:52px;border-radius:50%;cursor:pointer;
  background:rgba(255,255,255,0.12);border:1px solid rgba(255,255,255,0.25);
  color:#fff;font-size:1.5rem;line-height:1;display:grid;place-items:center;
}
.work-lb-btn:hover{background:rgba(255,255,255,0.22);}
.work-lb-prev{left:18px;}
.work-lb-next{right:18px;}
.work-lb-close{
  position:absolute;top:18px;right:18px;transform:none;
  width:46px;height:46px;font-size:1.4rem;
}

/* ---------- THE HOMEPAGE TEASER ---------- */
/* Three of the newest jobs, photograph first, as a route to the gallery rather
   than a gallery of its own -- so this one is a row of picture cards and the
   writing is cut to a line. */
.work-teaser{display:grid;grid-template-columns:repeat(3,1fr);gap:22px;}
.work-card{
  position:relative;
  display:flex;flex-direction:column;text-decoration:none;
  background:var(--white);border:1px solid var(--line);border-radius:var(--radius);
  overflow:hidden;transition:transform .18s ease,box-shadow .18s ease,border-color .18s ease;
}
.work-card:hover{transform:translateY(-3px);box-shadow:0 12px 30px rgba(11,42,74,0.10);border-color:var(--sky);}
/* The title's link is the card's link: stretched over the whole of it, so the
   card is one target even though it is not itself an anchor (see teaserCard in
   assets/work.js). Anything else in the card that is a link sits above it. */
.work-card-link{color:inherit;text-decoration:none;}
.work-card-link::after{content:'';position:absolute;inset:0;z-index:1;}
.work-card-link:focus-visible{outline:none;}
.work-card:has(.work-card-link:focus-visible){outline:3px solid var(--gold);outline-offset:2px;}
.work-card-shot{position:relative;aspect-ratio:3/2;background:var(--ice-soft);overflow:hidden;}
.work-card-shot img{width:100%;height:100%;object-fit:cover;display:block;}
.work-card-body{padding:18px 20px 22px;display:flex;flex-direction:column;gap:8px;}
.work-card .work-meta{margin:0;}
.work-card h3{font-size:1.06rem;line-height:1.25;}

/* A reviewed job: the same card, with the customer's words over the photograph.
   The photograph is laid behind the frame rather than in it, so a long review
   makes the frame taller instead of being cut off -- 3:2 is the least it will
   be, which is the shape every other card's photograph is.

   Readable words and a visible photograph pull opposite ways, so each is
   given its own part of the frame. The shade is attached to the words rather
   than to the frame: it starts clear a little above the first line and is at
   its darkest only behind the text, so however long the quote the photograph
   above it is left untouched. Behind the words the shade is kept light and
   unblurred so the photograph still reads through it; a soft dark halo on the
   letters does most of the work of lifting them off it. The quote is held to
   five lines so a long review cannot take over the picture; the whole of it is
   on Google. */
.work-card-shot.said{
  display:flex;flex-direction:column;justify-content:flex-end;
  min-height:0;background:#0B2A4A;
}
.work-card-shot.said img{position:absolute;inset:0;}
.work-card-quote{
  position:relative;
  display:flex;flex-direction:column;gap:7px;
  margin-top:72px;padding:44px 20px 15px;color:#fff;
  background:linear-gradient(to bottom,
    rgba(7,29,51,0) 0, rgba(7,29,51,0.24) 44px, rgba(7,29,51,0.34) 100%);
  text-shadow:0 1px 2px rgba(0,0,0,0.9), 0 0 6px rgba(7,29,51,0.95), 0 0 14px rgba(7,29,51,0.7);
}
.work-card-quote .stars{font-size:0.92rem;}
.work-card-quote .stars .under{color:rgba(255,255,255,0.35);}
.work-card-quote blockquote{
  margin:0;font-size:0.97rem;line-height:1.55;font-weight:500;letter-spacing:0.005em;
  display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:5;overflow:hidden;
}
.work-card-who{
  margin:0;display:flex;align-items:center;gap:7px;flex-wrap:wrap;
  font-family:'JetBrains Mono',monospace;font-size:0.68rem;letter-spacing:0.06em;
  text-transform:uppercase;color:rgba(255,255,255,0.85);
}
.work-card-who b{color:#fff;font-family:inherit;font-weight:700;}
.work-card-who .dot{color:rgba(255,255,255,0.45);}
/* Above the card's own stretched link, so it goes to Google and not the job. */
.work-card-who a{
  position:relative;z-index:2;color:#fff;font-weight:700;
  text-decoration:underline;text-decoration-color:var(--gold);text-underline-offset:3px;
}
.work-card-who a:hover{color:var(--gold);}
.work-card p{margin:0;color:var(--ink-soft);font-size:0.92rem;}
/* In the homepage timeline a job card shares a grid with review cards and with
   other job cards whose photographs carry a quote: the card fills its cell
   rather than stopping short of the one beside it. */
.proof-timeline .work-card{height:100%;}

/* ---------- THE THERMAL OVERLAY ---------- */
/* The canvas goes under the SVG, not over it. The map's whole job is to let
   somebody find their town on it, and a wash of colour on top of eleven-point
   type takes that away; underneath, the heat sits behind the names and the
   dashed ring still reads as the edge of the patch.
 
   Sized and positioned to match .map-frame svg exactly (92% of the frame,
   centred), because the points are in that SVG's viewBox units -- the script
   converts, and it can only convert if both boxes are the same box. */
.map-heat{
  position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);
  width:92%;height:92%;
  pointer-events:none;
  opacity:0;transition:opacity 1.1s ease;
}
.map-heat.on{opacity:1;}
.map-frame svg{position:relative;z-index:1;}

/* Once there is heat under the map, every name on it is navy type over whatever
   colour happens to be beneath it -- fine over the cool end, thinner over a
   bright orange Horbury. So the labels get a halo in the page's own paper colour,
   drawn behind the glyph rather than over it (paint-order), which keeps them
   legible without changing the map at all when the overlay is absent. */
.map-frame.has-heat svg text{
  paint-order:stroke;
  stroke:var(--paper);
  stroke-width:2.6px;
  stroke-linejoin:round;
}

/* The key. Without it the colour is a mood; with it the map is making a claim
   somebody can read -- six months, this many jobs, and these are the three
   places the warm end of it is about. */
.heat-key{
  margin-top:18px;display:flex;flex-direction:column;gap:10px;
}
.heat-scale{
  display:flex;align-items:center;gap:10px;
  font-family:'JetBrains Mono',monospace;font-size:0.7rem;
  letter-spacing:0.08em;text-transform:uppercase;color:var(--stone);font-weight:600;
}
.heat-scale i{
  flex:1;height:8px;border-radius:999px;
  background:linear-gradient(to right,#2b1063,#7b1e6e,#bc3754,#ed6925,#fbb61a,#fcffa4);
}
.heat-note{margin:0;font-size:0.9rem;color:var(--ink-soft);}
.heat-note b{color:var(--ink);}
.heat-busiest{
  display:flex;flex-wrap:wrap;gap:8px;margin:0;padding:0;list-style:none;
}
.heat-busiest li{
  display:inline-flex;align-items:baseline;gap:7px;
  border:1px solid var(--line);border-radius:999px;padding:5px 12px;
  background:var(--white);font-size:0.85rem;font-weight:600;color:var(--ink);
}
.heat-busiest li i{
  width:9px;height:9px;border-radius:50%;flex-shrink:0;align-self:center;
  box-shadow:0 0 0 2px rgba(255,255,255,0.7);
}
/* The name is the whole of a chip now that the job count has gone, so it keeps the
   body font and the ink colour it is set in rather than the small mono grey the
   count beside it used to be given. */
.heat-note a{color:var(--sky-deep);font-weight:600;}

/* ---------- NARROWER ---------- */
@media (max-width: 980px){
  .work-job,
  .work-job:nth-child(even of :not([hidden])){grid-template-columns:1fr;}
  /* Counted the same way as the swap it is undoing: plain nth-child(even) let a
     plate that only counts as even among the visible ones keep order:2, which in
     one column puts its photographs under the writing. */
  .work-job:nth-child(even of :not([hidden])) .work-shots{order:0;}
  /* Stacked, there is no writing alongside for the panel to take its height
     from, so the photographs carry their own shapes instead -- and that has to
     be the image's shape rather than the button's: a flex item whose only
     content is an absolutely positioned figure has no height to offer, and
     aspect-ratio on it resolves against a cross size that is not settled yet,
     which collapses the whole panel to nothing. So the figure goes back in flow
     down here and the img is the thing with the ratio. */
  .work-shots{min-height:0;}
  .work-shots > .work-shot,.work-rail,
  .work-shots.two > .work-shot:first-child,
  .work-shots.two > .work-shot + .work-shot{flex:0 0 auto;min-height:0;}
  .work-shot figure{position:static;}
  /* Capped, because a photograph the width of the page at its own ratio is 560px
     tall in a small laptop window and the plate it is on is meant to be getting
     shorter. Past the cap the shape gives way and the crop widens, which is the
     same bargain the side-by-side layout makes. */
  .work-shot img{height:auto;aspect-ratio:3/2;max-height:280px;}
  .work-rail .work-shot img{aspect-ratio:4/3;max-height:110px;}
  /* Two photographs go side by side rather than in bands once the plate is one
     column. Stacked they would be two full-width strips and half the panel's
     height again; beside each other they both keep three-to-two and the panel
     comes out shorter than either arrangement. Both tiles are named rather than
     matched by .work-shot alone, so that these shares outrank the reset above
     instead of losing the lead to it. */
  .work-shots.two{flex-direction:row;}
  .work-shots.two > .work-shot:first-child,
  .work-shots.two > .work-shot + .work-shot{flex:1 1 0;min-width:0;}
  .work-teaser{grid-template-columns:1fr 1fr;}
}
@media (max-width: 680px){
  .work-body{padding:24px 22px;}
  .work-teaser{grid-template-columns:1fr;}
  .work-lb-prev{left:6px;}
  .work-lb-next{right:6px;}
  /* Captions are a hover affordance on a desktop and unreachable on a phone, so
     on a touch layout they are simply shown -- but only on a photograph with
     room for one. Two tiles side by side on a 390px screen are 165px across, and
     a caption there is four lines of type over the whole of the picture it is
     describing. On those and on the rail's thumbnails the lightbox carries the
     caption, which is one tap away and has the photograph at full size. */
  .work-shot figcaption{opacity:1;transform:none;}
  .work-shots.two .work-shot figcaption,
  .work-rail .work-shot figcaption{display:none;}
}
