/* =========================================================================
   OUR WORK - the portfolio's own stylesheet
   =========================================================================

   THREE RULES GOVERN THIS FILE.  All three are checked by
   staging/tools/verify-portfolio.sh before a deploy, and all three exist so that ONE
   stylesheet can paint the public website AND the live card preview inside wp-admin.

   1. EVERY SELECTOR BEGINS ".work-".  No custom-property declaration block, no bare element
      selector, nothing that could reach into wp-admin and restyle a screen it knows nothing
      about.  (The verifier greps this file for the document-root selector and requires zero
      hits, so the name of it is deliberately not written down here either.)  Where a rule
      has to react to a section's ground colour it is written .work-strip.section-dark ...,
      never .section-dark .work-... - same result, and it still starts with .work-.

   2. NO RAW COLOUR VALUES.  Everything comes from the --qfm-* custom properties declared in
      style.css.  Custom properties INHERIT, which is the whole trick: admin/portfolio.css
      declares the same handful of properties on .qfm-work-preview, and the rules below then
      paint the preview card exactly as they paint the real one.  A hex typed in here would
      be a colour the admin copy could not reproduce.

   3. SELECTORS ARE FLUSH LEFT, EVEN INSIDE @media.  Rule 1 is enforced by a text check, and
      an indented selector is the obvious way to make that check either miss a violation or
      report one that is not there.  Deliberate; please keep it.

   HOUSE STYLE, briefly, so the section reads as part of this site and not as a bolted-on
   module: charcoal does the work, oak is punctuation and appears on interaction rather than
   at rest, corners are near-square (2-4px, this brand is planks), shadows are shallow and
   neutral, and WhatsApp green appears nowhere in this file - it belongs to the one WhatsApp
   button on a job page, which wears .btn-whatsapp from style.css.
   ========================================================================= */


/* ---------------------------------------------------------------- the grid

   Three across on a desktop, two at tablet, one on a phone.

   The two :has() rules at the foot of this file handle the case the fixed grid gets wrong:
   an archive holding ONE job would otherwise put a 365px card in the left third of a
   1152px row with two empty tracks beside it, which reads as a broken layout rather than as
   a young portfolio. */
.work-grid {
  display: grid;
  gap: 28px;
  align-items: stretch;
}

.work-grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }

/* Anchor target for #recent-work / #work, clear of the 90px sticky header. */
.work-strip { scroll-margin-top: 100px; }

.work-more { margin: 44px 0 0; text-align: center; }

/* .btn-secondary is authored for charcoal grounds - off-white label, translucent off-white
   border - so on ANY light ground (the material pages' plain white, or .section-alt's cream)
   it is off-white on off-white, i.e. an invisible button.  The !important is forced, not
   preferred: style.css sets `color: ... !important` on .btn-secondary and there is no other
   way to answer it from here. */
.work-more .btn-secondary {
  color: var(--qfm-ink) !important;
  border-color: var(--qfm-rule);
}

.work-more .btn-secondary:hover {
  color: var(--qfm-ink) !important;
  border-color: var(--qfm-oak);
  background: var(--qfm-ground);
}

.work-strip.section-dark .work-more .btn-secondary {
  color: var(--qfm-ground) !important;
  border-color: var(--qfm-rule-dark);
}

.work-strip.section-dark .work-more .btn-secondary:hover {
  color: var(--qfm-ground) !important;
  border-color: var(--qfm-oak);
  background: transparent;
}

/* The strip's own intro sits on charcoal on the homepage; .section-header p is stone grey,
   which is legible there but muddy.  Off-white at reduced opacity is the same idea the
   masthead lede uses. */
.work-strip.section-dark .section-header p {
  color: var(--qfm-ground);
  opacity: 0.74;
}


/* ---------------------------------------------------------------- the onward band

   ONE OR TWO JOBS.  The row will not render - correctly, a three-column grid holding one card
   looks broken - but with nothing in its place NOTHING on this website links to /our-work/,
   because the menu item is added by hand at cutover and every strip on the site is silent
   below three jobs.  His first job would be a live page nobody could reach.

   So the section stays and the grid is replaced by one link: same ground, same container,
   about a third of the height, no heading, no eyebrow.  It stands down on its own at the
   third job, when the real strip takes over.  Deliberately quiet - it is wayfinding, not a
   pitch, and the CTA banner is usually the next thing down the page.

   The link itself wears the archive's material-link treatment (further down this file), which
   is why the two are one rule and not two that drift apart. */
.work-onward.section { padding: 32px 0; }

.work-onward .work-more { margin: 0; }

.work-onward.section-dark .work-more a {
  background: transparent;
  border-color: var(--qfm-rule-dark);
  color: var(--qfm-ground);
}

.work-onward.section-dark .work-more a:hover {
  border-color: var(--qfm-oak);
  background: transparent;
  color: var(--qfm-ground);
}


/* ---------------------------------------------------------------- the card

   One component, three call sites (homepage strip, material-page strip, archive) plus the
   preview on the client's "Add a job" screen.  The whole card is one anchor, so the target
   is the photograph AND the words, which is what a thumb expects.

   The three data-work-field elements are emitted even when empty, so the preview's
   JavaScript always has somewhere to write.  :empty then hides them, which is why an empty
   summary never holds a line-height open on the live page. */
.work-card {
  display: flex;
  flex-direction: column;
  background: var(--qfm-surface);
  /* The archive AND the four material-page strips sit on plain white, where a white card with
     only --shadow-sm has no edge at all and the row reads as loose photographs.  A hairline is
     the site's own answer to this - .review-card is a white card with a hairline on a white
     section - and it costs no colour. */
  border: 1px solid var(--qfm-rule);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
  color: inherit;
  text-decoration: none;
  transition: transform var(--transition), box-shadow var(--transition);
}

/* .work-card:hover is (0,2,0) and beats style.css's a:hover at (0,1,1) - which is what stops
   the whole card, photograph included, being underlined and turned oak on hover. */
.work-card:hover {
  color: inherit;
  text-decoration: none;
  transform: translateY(-6px);
  box-shadow: var(--shadow-lg);
}

/* aspect-ratio here, not on the <img>, is what makes the row immune to layout shift: the
   card image can resolve to qfm-work-card (a hard 4:3 crop) OR - for the photographs
   uploaded before that size existed - to large/medium_large/full at whatever shape the
   phone took them.  The frame is 4:3 either way and the photo is covered into it. */
.work-card-media {
  position: relative;
  flex: 0 0 auto;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  background: var(--qfm-ground);
}

.work-card-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 0.6s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

.work-card:hover .work-card-media img { transform: scale(1.06); }

/* The hairline between photograph and words is the plank joint the wordmark is cut with.
   It is where this card spends its oak, and only on hover - punctuation, not decoration. */
.work-card-body {
  display: flex;
  flex-direction: column;
  flex: 1;
  padding: 20px 22px 22px;
  border-top: 1px solid var(--qfm-rule);
  transition: border-color var(--transition);
}

.work-card:hover .work-card-body { border-top-color: var(--qfm-oak); }

/* Ink at 0.62 rather than --qfm-quiet: quiet grey on white measures 3.6:1, which fails AA for
   text this size, and the meta line is the only place the town and the month appear on a card.
   Composited it lands near #747474, 4.67:1, and reads as the same order of quietness.  (Over
   the cream ground the same declaration composites to #706E6B on #F5EFE6, which is 4.45:1 and
   under AA.  The material strips moving from section-alt to plain white therefore fixed a
   contrast failure as well as the merged band - see inc/material-page.php.) */
.work-card-meta {
  display: block;
  margin-bottom: 9px;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--qfm-ink);
  opacity: 0.62;
}

.work-card-title {
  margin: 0;
  font-family: var(--font-heading);
  font-size: 20px;
  font-weight: 900;
  line-height: 1.18;
  letter-spacing: -0.015em;
  color: var(--qfm-ink);
  overflow-wrap: break-word;
}

/* Clamped to three lines so a fitter who writes eighty words and one who writes twelve
   produce the same shaped row.  The full write-up is one tap away and that is the point. */
.work-card-sum {
  margin: 10px 0 0;
  font-size: 14.5px;
  line-height: 1.6;
  color: var(--qfm-text);
  overflow-wrap: break-word;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  overflow: hidden;
}

.work-card-meta:empty,
.work-card-title:empty,
.work-card-sum:empty { display: none; }

/* margin-top:auto pins "See the photos" to the bottom of every card in the row, whatever
   length the titles above it happen to be. */
.work-card .card-more {
  margin-top: auto;
  padding-top: 14px;
  color: var(--qfm-ink);
}

.work-card:hover .card-more {
  color: var(--qfm-oak-deep);
  transform: translateX(3px);
}


/* ---------------------------------------------------------------- the card on charcoal

   On the homepage strip the section is dark and the card LOSES ITS BOX: a white panel on
   charcoal would fight the photograph, and the photograph is the card.  Finished-floor
   photography also reads far better on charcoal than on cream, which is why that strip is
   the one dark band between the subfloor block and the reviews. */
.work-strip.section-dark .work-card {
  background: transparent;
  border-color: transparent;
  box-shadow: none;
}

.work-strip.section-dark .work-card:hover { box-shadow: none; }

.work-strip.section-dark .work-card-body {
  padding-left: 0;
  padding-right: 0;
  border-top-color: var(--qfm-rule-dark);
}

.work-strip.section-dark .work-card:hover .work-card-body { border-top-color: var(--qfm-oak); }

.work-strip.section-dark .work-card-title { color: var(--qfm-ground); }

.work-strip.section-dark .work-card-meta,
.work-strip.section-dark .work-card-sum {
  color: var(--qfm-ground);
  opacity: 0.66;
}

.work-strip.section-dark .work-card .card-more { color: var(--qfm-ground); }

.work-strip.section-dark .work-card:hover .card-more { color: var(--qfm-oak); }


/* ---------------------------------------------------------------- archive masthead

   A charcoal typographic band, and deliberately no photograph.  Any banner here would have
   to be stock - on the one page whose entire value is that nothing on it is stock.  The oak
   hairline along the bottom is the same device the trust bar uses. */
.work-masthead {
  background: var(--qfm-ink);
  border-bottom: 1px solid var(--qfm-oak);
  padding: 96px 0 66px;
  text-align: center;
}

.work-masthead h1 {
  max-width: 900px;
  margin: 0 auto;
  font-size: 46px;
  line-height: 1.06;
  letter-spacing: -0.025em;
  color: var(--qfm-ground);
}

.work-lede {
  max-width: 680px;
  margin: 18px auto 0;
  font-size: 18px;
  line-height: 1.7;
  color: var(--qfm-ground);
  opacity: 0.72;
}

/* The archive renders at zero jobs on purpose - it is noindexed rather than 404'd - so this
   is a real state somebody will see, not a placeholder. */
.work-empty {
  max-width: 640px;
  margin: 0 auto;
  padding: 6px 0;
  text-align: center;
}

.work-empty h2 { font-size: 30px; }

.work-empty p {
  margin-top: 14px;
  font-size: 17px;
  line-height: 1.75;
  color: var(--qfm-text);
}

/* Four real links to the four pages that already rank, rather than filter chips that would
   mint competing URLs.  44px tall because they are the main onward route from this page. */
.work-materials {
  margin-top: 60px;
  padding-top: 34px;
  border-top: 1px solid var(--qfm-rule);
  text-align: center;
}

.work-materials-h {
  margin: 0 0 18px;
  font-size: 15px;
  color: var(--qfm-text);
}

.work-materials ul {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Shared with the onward band at the top of this file: both are a quiet, hairline-boxed
   wayfinding link, 44px tall, oak only on hover.  One rule so they cannot drift apart. */
.work-materials a,
.work-onward .work-more a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 20px;
  border: 1px solid var(--qfm-rule);
  border-radius: var(--radius-md);
  background: var(--qfm-surface);
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--qfm-ink);
  transition: border-color var(--transition), background var(--transition);
}

.work-materials a:hover,
.work-onward .work-more a:hover {
  border-color: var(--qfm-oak);
  background: var(--qfm-ground);
  color: var(--qfm-ink);
  text-decoration: none;
}

/* At twelve a page, three hundred jobs is twenty-five of these.  Every control is 44px. */
.work-pagination {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 6px;
  margin-top: 48px;
}

.work-pagination .page-numbers {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  padding: 0 12px;
  border: 1px solid var(--qfm-rule);
  border-radius: var(--radius-md);
  background: var(--qfm-surface);
  font-size: 15px;
  font-weight: 700;
  color: var(--qfm-ink);
  transition: border-color var(--transition);
}

.work-pagination a.page-numbers:hover {
  border-color: var(--qfm-oak);
  color: var(--qfm-ink);
  text-decoration: none;
}

.work-pagination .current {
  background: var(--qfm-ink);
  border-color: var(--qfm-ink);
  color: var(--qfm-ground);
}

/* Ink at 0.62, not --qfm-quiet, for exactly the reason the card meta line gives above:
   #8A8581 on white measures 3.65:1 and fails AA, and an ellipsis a partially-sighted reader
   cannot see is a gap in the middle of the page control.  Composited this is #747474 at
   4.67:1 and still reads a clear step quieter than the numbered links, which are full ink
   at 700. */
.work-pagination .dots {
  border-color: transparent;
  background: transparent;
  color: var(--qfm-ink);
  opacity: 0.62;
}

/* Administrators only, and never rendered for a visitor - the fourth way into "Add a job"
   for somebody holding a phone, where the wp-admin sidebar is behind a hamburger. */
.work-admin-add {
  margin: 0 0 26px;
  text-align: right;
  font-size: 14px;
}

.work-admin-add a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-weight: 700;
  color: var(--qfm-oak-deep);
}


/* ---------------------------------------------------------------- one job */

.work-single { padding-bottom: 10px; }

.work-crumb {
  padding-top: 26px;
  font-size: 14px;
}

.work-crumb a {
  color: var(--qfm-text);
  font-weight: 600;
  letter-spacing: 0.02em;
}

/* The same link at the FOOT of a job page, on jobs with no row of three related jobs to carry
   it - see single-qfm_job.php. It sits directly above the dark CTA banner, so it needs air
   under it that the top one does not. */
.work-crumb--foot {
  padding-top: 34px;
  padding-bottom: 44px;
}

.work-lead-wrap { margin-top: 22px; }

.work-lead {
  margin: 0;
  text-align: center;
}

/* 🔴 CAPPED BY HEIGHT, NEVER CROPPED.  qfm-work-lead is a soft size, so a portrait
   staircase arrives portrait.  Letting it run at container width would make it 1.5 screens
   tall; cropping it with object-fit would cut the sides off the one photograph the job page
   is built around - and the gallery below deliberately does not repeat the cover, so a
   cropped cover could never be seen whole.  max-height + width:auto scales it down
   proportionally and centres it instead.  The width/height attributes on the tag give the
   browser the ratio, and 74vh is known before layout, so neither path shifts anything. */
.work-lead-img {
  display: block;
  margin: 0 auto;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 74vh;
  border-radius: var(--radius-md);
}

.work-head { padding-top: 34px; }

.work-head h1 {
  max-width: 900px;
  font-size: 42px;
  line-height: 1.08;
  letter-spacing: -0.025em;
  overflow-wrap: break-word;
}

.work-chips { margin: 16px 0 0; }

.work-chip {
  display: inline-block;
  padding: 5px 12px;
  border-left: 3px solid var(--qfm-rule);
  border-radius: var(--radius-sm);
  background: var(--qfm-ground);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--qfm-ink);
}

.work-chip--commercial {
  border-left-color: var(--qfm-oak);
  color: var(--qfm-ink);
}

/* 🔴 THE HAIRLINES ARE A RING AROUND EACH CELL, NOT THE GRID GAP SHOWING THROUGH.
   The gap-as-border trick is the obvious way to build this and it is wrong here: the number
   of columns is auto-fit, so five facts on a phone wrap to 2 x 2 + 1 and the SIXTH grid area
   has no cell in it - which painted a solid block of rule colour next to "Range". Measured on
   a 390px render, not imagined.

   A 1px box-shadow ring on each cell draws the same hairlines (adjacent rings overlap inside
   the 1px gap and land as one line, and the edge rings form the outer frame), and an empty
   grid area shows the container's own surface colour, i.e. nothing. Works at any column
   count, for any number of facts, with no media query. */
.work-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 1px;
  margin: 28px 1px 0;
  padding: 0;
  list-style: none;
  background: var(--qfm-surface);
}

.work-fact {
  padding: 15px 18px;
  background: var(--qfm-surface);
  box-shadow: 0 0 0 1px var(--qfm-rule);
}

.work-fact-k {
  display: block;
  margin-bottom: 5px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--qfm-ink);
  opacity: 0.62;
}

.work-fact-v {
  display: block;
  font-size: 16px;
  font-weight: 700;
  line-height: 1.35;
  color: var(--qfm-ink);
  overflow-wrap: break-word;
}

/* 🔴 THE MEASURE IS CAPPED ON THE PARAGRAPH, NOT ON THE CONTAINER.  Narrowing .work-body
   itself re-centres the whole block, so its left edge landed 180px inside the <h1> and the
   facts strip above it and the write-up read as though it had been indented by accident.
   Capping the paragraphs keeps the column left-aligned with everything else on the page while
   still holding the line length to something readable. */
.work-body { margin-top: 40px; }

.work-body p {
  max-width: 800px;
  margin: 0 0 20px;
  font-size: 17px;
  line-height: 1.8;
  color: var(--qfm-text);
  overflow-wrap: break-word;
}

.work-body p:first-child {
  font-size: 19px;
  line-height: 1.7;
  color: var(--qfm-ink);
}

.work-body p:last-child { margin-bottom: 0; }

/* CSS columns, not a grid.  A grid would need one aspect ratio for every shot, and cropping
   is exactly what a soft gallery size exists to avoid: a stair job is portrait, a lounge is
   landscape, and both are his.  Columns pack them with no gaps and no JavaScript, and
   collapse to a single column on a phone where full-bleed is what you want anyway. */
.work-gallery {
  margin: 46px 0 0;
  padding: 0;
  list-style: none;
  columns: 2;
  column-gap: 16px;
}

.work-shot {
  margin: 0 0 16px;
  -webkit-column-break-inside: avoid;
  page-break-inside: avoid;
  break-inside: avoid;
}

.work-shot a {
  display: block;
  border-radius: var(--radius-md);
  overflow: hidden;
}

.work-shot img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius-md);
  background: var(--qfm-ground);
  transition: opacity var(--transition);
}

.work-shot a:hover img { opacity: 0.9; }

.work-townlink {
  margin-top: 46px;
  padding-top: 26px;
  border-top: 1px solid var(--qfm-rule);
}

.work-townlink p {
  margin: 0;
  max-width: 800px;
  font-size: 16px;
  line-height: 1.7;
  color: var(--qfm-text);
}

.work-townlink a {
  font-weight: 700;
  color: var(--qfm-ink);
  border-bottom: 1px solid var(--qfm-oak);
}

.work-townlink a:hover {
  color: var(--qfm-oak-deep);
  border-bottom-color: var(--qfm-oak-deep);
  text-decoration: none;
}

.work-related .work-more { margin-top: 40px; }


/* ---------------------------------------------------------------- responsive
   Selectors stay flush left inside @media - see rule 3 at the top of this file. */

@media (max-width: 1100px) {
.work-masthead h1 { font-size: 40px; }
.work-head h1 { font-size: 36px; }
}

@media (max-width: 992px) {
.work-grid--3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.work-masthead { padding: 80px 0 54px; }
.work-masthead h1 { font-size: 34px; }
.work-head h1 { font-size: 32px; }
.work-body { margin-top: 32px; }
}

@media (max-width: 768px) {
.work-masthead { padding: 66px 0 44px; }
.work-masthead h1 { font-size: 30px; }
.work-lede { font-size: 16px; }
.work-lead-wrap { margin-top: 16px; }
.work-lead-img { max-height: none; }
.work-head { padding-top: 26px; }
.work-head h1 { font-size: 28px; }
.work-body p { font-size: 16.5px; }
.work-body p:first-child { font-size: 17.5px; }
.work-gallery { columns: 1; }
.work-materials { margin-top: 44px; }
.work-admin-add { text-align: left; }
}

@media (max-width: 700px) {
.work-grid { gap: 22px; }
.work-grid--3 { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 480px) {
.work-masthead h1 { font-size: 26px; }
.work-head h1 { font-size: 25px; }
.work-card-body { padding: 18px 18px 20px; }
}

/* One or two cards must not sit in a three-column frame with a third of the row left empty.
   Scoped above the single-column breakpoint so it can never turn a phone back into two
   columns; where :has() is unsupported the rule is simply ignored and the layout is the
   plain three-column one it would have been anyway. */
@media (min-width: 701px) {
.work-grid--3:not(:has(> .work-card:nth-child(2))) {
  grid-template-columns: minmax(0, 420px);
  justify-content: center;
}
.work-grid--3:has(> .work-card:nth-child(2)):not(:has(> .work-card:nth-child(3))) {
  grid-template-columns: repeat(2, minmax(0, 420px));
  justify-content: center;
}
}

/* Every animation here is decoration; the OS setting wins. */
@media (prefers-reduced-motion: reduce) {
.work-card,
.work-card-body,
.work-card-media img,
.work-shot img,
.work-materials a,
.work-onward .work-more a,
.work-pagination .page-numbers { transition: none; }
.work-card:hover { transform: none; }
.work-card:hover .work-card-media img { transform: none; }
.work-card:hover .card-more { transform: none; }
}
