/* Seabright Mechanical — shared site styles.
   Base tokens and components lifted verbatim from the original index.html so
   the look does not shift; everything below the BASE section is additive. */
:root{
  --navy:#06202F; --navy2:#052030; --steel:#105F8A; --steel-l:#1f7cb0;
  --ink:#0c2330; --muted:#5b6b75; --line:#dde4e9; --bg:#ffffff; --soft:#f4f7f9;
}
*{box-sizing:border-box}
html{scroll-behavior:smooth}
body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;color:var(--ink);background:var(--bg);line-height:1.6;}
a{color:var(--steel);text-decoration:none}
img{max-width:100%;display:block}
.container{max-width:1080px;margin:0 auto;padding:0 24px}
h1,h2,h3{line-height:1.2;color:var(--navy);margin:0}
.btn{display:inline-block;font-weight:700;border-radius:8px;padding:13px 22px;cursor:pointer;border:2px solid var(--steel);transition:.15s;font-size:15px}
.btn-primary{background:var(--steel);color:#fff}
.btn-primary:hover{background:var(--steel-l);border-color:var(--steel-l)}
.btn-ghost{background:transparent;color:var(--steel)}
.btn-ghost:hover{background:var(--soft)}
.btn-light{background:#fff;color:var(--navy);border-color:#fff}
.btn-light:hover{background:#eaf1f6}
.btn-outline-light{background:transparent;color:#fff;border-color:rgba(255,255,255,.6)}
.btn-outline-light:hover{border-color:#fff;background:rgba(255,255,255,.08)}

/* NAV */
header.nav{position:sticky;top:0;z-index:50;background:rgba(255,255,255,.96);backdrop-filter:blur(6px);border-bottom:1px solid var(--line)}
.nav-inner{display:flex;align-items:center;justify-content:space-between;height:68px}
.nav-inner .logo{height:38px;width:auto}
.nav-links{display:flex;align-items:center;gap:24px}
.nav-links a{color:var(--navy);font-weight:600;font-size:15px}
.nav-links a:hover{color:var(--steel)}
.nav-links .btn{padding:9px 16px;font-size:14px;border-width:2px}
.nav-links .btn:hover{color:#fff}
/* 44px square. It was 35x33, which is under every published minimum for a
   touch target and is the only control a phone visitor has for the whole
   navigation. Nothing moves visually: the glyph is the same size, the box
   around it is not. */
.menu-btn{display:none;background:none;border:none;cursor:pointer;font-size:26px;color:var(--navy);
  width:44px;height:44px;line-height:1;padding:0;margin-right:-8px;
  align-items:center;justify-content:center}

/* HERO */
.hero{background:linear-gradient(125deg,var(--navy) 0%,var(--navy2) 45%,var(--steel) 130%);color:#fff;padding:84px 0 92px;position:relative;overflow:hidden}
.hero:after{content:"";position:absolute;right:-120px;top:-120px;width:420px;height:420px;border-radius:50%;background:radial-gradient(circle,rgba(31,124,176,.35),transparent 70%)}
.hero .container{position:relative;z-index:2;max-width:820px}
.hero .eyebrow{text-transform:uppercase;letter-spacing:3px;font-size:13px;color:#8fc4e2;font-weight:700;margin-bottom:18px}
.hero h1{color:#fff;font-size:46px;font-weight:800;letter-spacing:.3px}
.hero p.lead{font-size:20px;color:#d6e6f0;margin:20px 0 32px;max-width:660px}
.hero .cta{display:flex;gap:14px;flex-wrap:wrap;align-items:center}
/* .18, not .28. At .28 the machine in the photograph carries its maker's name
   legibly across the headline of a shop that competes with it, and the lead
   paragraph sat at about 4.4:1 over the lighter part of the image. Lower is
   both fixes. The picture is still doing its job: it is atmosphere. */
.hero-bg{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;z-index:0;opacity:.18}
/* The four supporting claims under the hero headline. A list, because that is
   what it is, and a checkmark rather than a bullet because each one is a thing
   this shop does rather than a topic. They sit above the buttons so the reader
   has a reason to press one. */
.hero-pts{list-style:none;padding:0;margin:22px 0 26px;display:flex;flex-wrap:wrap;gap:10px 26px}
.hero-pts li{position:relative;padding-left:24px;color:#d6e6f0;font-size:15.5px;font-weight:600}
.hero-pts li:before{content:"✓";position:absolute;left:0;color:#8fc4e2;font-weight:800}
/* Sits between the checklist and the buttons, so it needs air on both sides;
   with margin only on top it butted straight into the call to action. */
.hero-sub{color:#c8dcea;font-size:14.5px;margin:4px 0 22px;max-width:620px}
.about-media{margin-top:28px;border-radius:14px;overflow:hidden;max-height:380px;box-shadow:0 10px 30px rgba(6,32,47,.12)}
.about-media img{width:100%;height:100%;object-fit:cover;display:block}

/* SECTIONS */
section{padding:74px 0}
.section-soft{background:var(--soft)}
.sec-head{max-width:720px;margin-bottom:44px}
.sec-head .eyebrow{text-transform:uppercase;letter-spacing:2.5px;font-size:12px;color:var(--steel);font-weight:700;margin-bottom:12px}
.sec-head h2{font-size:34px}
.sec-head p{color:var(--muted);font-size:18px;margin-top:14px}

.grid{display:grid;gap:22px}
.grid-3{grid-template-columns:repeat(3,1fr)}
.grid-2{grid-template-columns:repeat(2,1fr)}
.card{background:#fff;border:1px solid var(--line);border-radius:12px;padding:28px}
.section-soft .card{box-shadow:0 1px 3px rgba(6,32,47,.04)}
.card .ic{width:44px;height:44px;border-radius:10px;background:linear-gradient(135deg,var(--navy),var(--steel));display:flex;align-items:center;justify-content:center;margin-bottom:16px}
.card .ic svg{width:24px;height:24px;stroke:#fff;fill:none;stroke-width:2}
.card h3{font-size:19px;margin-bottom:8px}
.card p{color:var(--muted);font-size:15px;margin:0}

.specs{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:0;border:1px solid var(--line);border-radius:12px;overflow:hidden;background:#fff}
/* min-width:0 so a track can never be forced wider than its share by its own
   contents. A bare 1fr is minmax(auto,1fr), and auto floors the track at the
   min-content width of the widest thing in it. The values here are set in
   22px/800 and two of them are one unbreakable token: "350&nbsp;x&nbsp;320&nbsp;x&nbsp;320"
   has no break opportunity at all, and "engineering" has none either. Below
   about 430px that min-content width exceeded the track, every track grew to
   match, and the grid pushed past the container and scrolled the page
   sideways. overflow-wrap is the second half of the same fix: with the floor
   removed the track can shrink, so the token has to be allowed to fold rather
   than spill out of the box it no longer fits. */
.specs .item{padding:26px 22px;text-align:center;min-width:0}
.specs .num,.specs .lbl{overflow-wrap:break-word}
.specs .item + .item{border-left:1px solid var(--line)}
.specs .num{font-size:30px;font-weight:800;color:var(--steel);line-height:1}
/* for tiles whose value is words or a three-axis dimension, not one number */
.specs .num.sm{font-size:22px;line-height:1.2}
.specs .lbl{color:var(--muted);font-size:14px;margin-top:8px}

.chips{display:flex;flex-wrap:wrap;gap:8px;margin-top:16px}
.chips span{background:#e7f0f6;color:var(--steel);font-size:13px;font-weight:700;padding:5px 12px;border-radius:20px}

/* forms */
form .row{display:grid;grid-template-columns:1fr 1fr;gap:16px}
form .field{margin-bottom:16px;display:flex;flex-direction:column}
form label{font-size:13px;font-weight:600;color:var(--navy);margin-bottom:6px}
form input,form textarea,form select{font:inherit;border:1px solid var(--line);border-radius:8px;padding:11px 13px;color:var(--ink);background:#fff}
form input:focus,form textarea:focus,form select:focus{outline:none;border-color:var(--steel);box-shadow:0 0 0 3px rgba(16,95,138,.12)}
form textarea{min-height:120px;resize:vertical}
.form-card{background:#fff;border:1px solid var(--line);border-radius:14px;padding:30px;box-shadow:0 6px 24px rgba(6,32,47,.06)}
.quote-wrap{display:grid;grid-template-columns:1fr 1.1fr;gap:46px;align-items:start}
.quote-wrap .lead-col h2{font-size:32px}
.quote-wrap .lead-col p{color:var(--muted);font-size:17px;margin-top:14px}
.quote-wrap ul{list-style:none;padding:0;margin:22px 0 0}
.quote-wrap ul li{padding:8px 0 8px 30px;position:relative;color:var(--ink)}
.quote-wrap ul li:before{content:"✓";position:absolute;left:0;color:var(--steel);font-weight:800}

/* footer */
footer{background:var(--navy);color:#c4d4de;padding:46px 0 30px;font-size:14px}
footer .f-top{display:flex;justify-content:space-between;gap:30px;flex-wrap:wrap;align-items:flex-start;padding-bottom:26px;border-bottom:1px solid rgba(255,255,255,.12)}
footer img{height:34px}
footer .f-links{display:flex;gap:26px;flex-wrap:wrap}
footer .f-links a{color:#c4d4de}
footer .f-links a:hover{color:#fff}
footer .f-bottom{padding-top:20px;display:flex;justify-content:space-between;gap:14px;flex-wrap:wrap;color:#8aa3b2}
.note{font-size:13px;color:var(--muted);margin-top:14px}

/* ============================================================
   ADDITIONS — new components, same visual language
   ============================================================ */

/* page header for interior pages */
.page-head{background:linear-gradient(125deg,var(--navy) 0%,var(--navy2) 60%,var(--steel) 165%);color:#fff;padding:56px 0 60px}
.page-head .eyebrow{text-transform:uppercase;letter-spacing:3px;font-size:12px;color:#8fc4e2;font-weight:700;margin-bottom:14px}
.page-head h1{color:#fff;font-size:38px;font-weight:800;max-width:840px}
.page-head p{color:#d6e6f0;font-size:19px;margin:16px 0 0;max-width:720px}
.page-head .cta{display:flex;gap:12px;flex-wrap:wrap;margin-top:26px}

/* breadcrumbs */
.crumbs{font-size:13px;color:var(--muted);padding:16px 0 0}
.crumbs a{color:var(--muted)}
.crumbs a:hover{color:var(--steel)}
.crumbs span{margin:0 7px;opacity:.6}

/* three-step workflow */
.steps{display:grid;grid-template-columns:repeat(3,1fr);gap:22px;counter-reset:step}
.step{background:#fff;border:1px solid var(--line);border-radius:12px;padding:28px;position:relative}
.step .n{width:34px;height:34px;border-radius:50%;background:var(--steel);color:#fff;font-weight:800;font-size:15px;display:flex;align-items:center;justify-content:center;margin-bottom:14px}
.step h3{font-size:18px;margin-bottom:8px}
.step p{color:var(--muted);font-size:15px;margin:0}
.step.step-review{border-color:#bcd6e6;background:linear-gradient(180deg,#f7fbfd,#fff)}

/* callout / disclaimer strip */
.callout{border:1px solid var(--line);border-left:4px solid var(--steel);background:var(--soft);border-radius:10px;padding:18px 22px;color:var(--ink);font-size:15px}
.callout strong{color:var(--navy)}
.callout.tight{padding:13px 16px;font-size:13.5px;line-height:1.55}

/* engineer band */
.band{background:var(--navy);color:#dce8f0;padding:64px 0}
.band h2{color:#fff;font-size:32px;max-width:760px}
.band p{color:#b8cddb;font-size:18px;margin-top:16px;max-width:720px}
.band .pts{display:grid;grid-template-columns:repeat(2,1fr);gap:14px 34px;margin-top:30px;max-width:820px}
.band .pts div{padding-left:28px;position:relative;font-size:15.5px}
.band .pts div:before{content:"→";position:absolute;left:0;color:#61a9d3;font-weight:700}
.band .cta{margin-top:32px;display:flex;gap:14px;flex-wrap:wrap}

/* showcase cards */
.shots{display:grid;grid-template-columns:repeat(3,1fr);gap:22px}
.shot{border:1px solid var(--line);border-radius:12px;overflow:hidden;background:#fff;display:flex;flex-direction:column}
.shot .thumb{aspect-ratio:4/3;background:var(--soft);display:flex;align-items:center;justify-content:center;border-bottom:1px solid var(--line);position:relative;overflow:hidden}
.shot .thumb img{width:100%;height:100%;object-fit:cover}
.shot .thumb .ph{color:var(--muted);font-size:13px;text-align:center;padding:18px}
.shot .thumb .ph b{display:block;font-size:26px;opacity:.35;margin-bottom:6px;color:var(--navy)}
.shot .body{padding:22px;flex:1;display:flex;flex-direction:column}
.shot .tag{display:inline-block;background:#e7f0f6;color:var(--steel);font-size:11.5px;font-weight:700;letter-spacing:.6px;padding:4px 10px;border-radius:20px;text-transform:uppercase;align-self:flex-start}
.shot h3{font-size:18px;margin:12px 0 8px}
.shot p{color:var(--muted);font-size:14.5px;margin:0 0 16px;flex:1}
.shot .more{font-weight:700;font-size:14px}

/* spec table used on case studies + material pages */
.spec-table{width:100%;border-collapse:collapse;font-size:15px;background:#fff;border:1px solid var(--line);border-radius:12px;overflow:hidden}
.spec-table th,.spec-table td{text-align:left;padding:13px 16px;border-bottom:1px solid var(--line);vertical-align:top}
.spec-table tr:last-child th,.spec-table tr:last-child td{border-bottom:0}
.spec-table th{background:var(--soft);color:var(--navy);font-weight:700;width:34%;white-space:nowrap}
.spec-table td{color:var(--muted)}
.spec-table td strong{color:var(--ink)}
.table-wrap{overflow-x:auto}

/* prose blocks on service / case-study pages */
.prose{max-width:760px}
.prose h2{font-size:28px;margin:44px 0 14px}
.prose h3{font-size:20px;margin:30px 0 10px}
.prose p{color:var(--muted);font-size:16.5px;margin:0 0 16px}
.prose ul,.prose ol{color:var(--muted);font-size:16.5px;padding-left:22px;margin:0 0 18px}
.prose li{margin-bottom:8px}
.prose strong{color:var(--ink)}
.prose > :first-child{margin-top:0}

/* ---------- DFAM resources page ----------
   Used by dfam-resources.html only, which is generated from
   tools/pages/resources.mjs. Two things live here.

   .dfam-fig is the frame around the three inline SVG diagrams. The SVGs carry a
   viewBox and no width or height, so width:100% with height:auto lets them
   scale to whatever column they are in without a media query. Their colours are
   the same custom properties the rest of the stylesheet uses, so they follow the
   site rather than carrying a palette of their own. font-family:inherit on the
   text matters: SVG text does NOT inherit the body font from the document by
   default in every engine, and without it the labels come out in a serif.

   .dfam-links is the outbound list. .prose ul sets a padding-left and its
   selector is (0,1,1), so the reset below has to be at least as specific or the
   bullets come back; ul.dfam-links inside .prose is (0,2,1). */
.dfam-fig{margin:0 0 26px;border:1px solid var(--line);border-radius:12px;background:var(--soft);padding:18px 18px 14px}
.dfam-fig svg{width:100%;height:auto;display:block}
.dfam-fig svg text{font-family:inherit}
.dfam-fig figcaption{font-size:13.5px;color:var(--muted);margin-top:14px;line-height:1.55}
/* .dfam-photo is the same frame around a photograph instead of a diagram. The
   img carries its intrinsic width and height so the space is reserved before
   the bytes arrive, and width:100%/height:auto lets it scale anyway.
   .photo-credit is the attribution, on its own line under the caption. It is
   part of the figcaption, not a sibling of it, so a screen reader reads the
   picture and its source as one thing. */
.dfam-photo img{width:100%;height:auto;display:block;border-radius:8px}
.dfam-photo .photo-credit{display:block;margin-top:5px;font-size:12.5px;font-style:italic}
.prose ul.dfam-links{list-style:none;padding:0;margin:0 0 14px;font-size:inherit}
.prose ul.dfam-links li{border-bottom:1px solid var(--line);padding:15px 0;margin:0}
.prose ul.dfam-links li:last-child{border-bottom:0}
.dfam-links .pub{display:block;font-size:11.5px;text-transform:uppercase;letter-spacing:1.4px;color:var(--steel);font-weight:700;margin-bottom:5px}
.dfam-links a{font-weight:700;font-size:16.5px;color:var(--navy)}
.dfam-links a:hover{color:var(--steel)}
.dfam-links .desc{display:block;font-size:14.5px;color:var(--muted);margin-top:5px;line-height:1.5}
/* (0,2,0), because .prose p is (0,1,1) and would otherwise win on both the
   size and the margin. */
.prose p.dfam-credit{font-size:13.5px;color:var(--muted);margin:0 0 34px;font-style:italic}
.prose p.dfam-note{font-size:14px;color:var(--muted);background:var(--soft);border:1px solid var(--line);border-radius:10px;padding:12px 15px;margin:0 0 8px}

/* two-column layout with a sticky sidebar */
.split{display:grid;grid-template-columns:1fr 320px;gap:48px;align-items:start}
.side{position:sticky;top:92px}
.side .card{padding:24px}
.side h3{font-size:17px;margin-bottom:10px}
.side p{font-size:14.5px;color:var(--muted);margin:0 0 16px}
.side .btn{width:100%;text-align:center}
.side ul{list-style:none;padding:0;margin:0}
.side ul li{padding:7px 0;border-bottom:1px solid var(--line);font-size:14.5px}
.side ul li:last-child{border-bottom:0}

/* placeholder marker — obvious in review, invisible in intent */
.todo{background:#fff8e6;border:1px dashed #e0b34a;color:#7a5a12;border-radius:8px;padding:12px 15px;font-size:13.5px;margin:0 0 18px}
.todo b{color:#5d4409}

/* final CTA strip */
.cta-strip{background:linear-gradient(125deg,var(--navy),var(--steel) 160%);color:#fff;padding:56px 0;text-align:center}
.cta-strip h2{color:#fff;font-size:30px}
.cta-strip p{color:#cfe3ef;font-size:17px;margin:14px auto 26px;max-width:620px}
.cta-strip .cta{display:flex;gap:14px;justify-content:center;flex-wrap:wrap}

/* ---------- page-head spec block ----------
   The navy band used to be a grid with copy in the left column and nothing at
   all in the right five hundred pixels, which read as a component that had
   failed to load. It now carries the shop's hard numbers, which is also the
   first thing an engineer wants and used to be reachable only by uploading a
   file to the estimator. Below 980px it stacks under the copy. */
.page-head .ph-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(300px,360px);gap:40px;align-items:center}
.page-head .ph-main{min-width:0}
.spec-panel{background:rgba(255,255,255,.07);border:1px solid rgba(255,255,255,.18);
  border-radius:14px;padding:20px 22px;color:#cfe2ee;font-size:13.5px;line-height:1.55}
.spec-panel h2{color:#fff;font-size:12px;font-weight:700;text-transform:uppercase;
  letter-spacing:2px;margin:0 0 14px}
.spec-panel dl{margin:0}
.spec-panel dt{color:#8fc4e2;font-size:11.5px;font-weight:700;text-transform:uppercase;
  letter-spacing:1.2px;margin-top:12px}
.spec-panel dt:first-child{margin-top:0}
.spec-panel dd{margin:3px 0 0}
.spec-panel .foot{margin-top:16px;padding-top:13px;border-top:1px solid rgba(255,255,255,.16);color:#b8cddb}
@media (max-width:880px){
  .page-head .ph-grid{grid-template-columns:1fr;gap:28px}
}

@media (max-width:880px){
  .grid-3{grid-template-columns:1fr 1fr}
  .steps{grid-template-columns:1fr}
  .shots{grid-template-columns:1fr 1fr}
  .split{grid-template-columns:1fr;gap:34px}
  .side{position:static}
  .specs{grid-template-columns:repeat(2,minmax(0,1fr))}
  .specs .item:nth-child(odd){border-left:none}
  .specs .item:nth-child(n+3){border-top:1px solid var(--line)}
  .quote-wrap{grid-template-columns:1fr;gap:30px}
  .band .pts{grid-template-columns:1fr}
}
@media (max-width:640px){
  /* One tile per row on a phone. At 390px a container is 342px wide and a
     two up tile is 171px, of which 44px is padding: 127px of text for a value
     that measures about 156px. Stacking gives each value the full 298px, so
     nothing has to fold and nothing overflows. */
  .specs{grid-template-columns:minmax(0,1fr)}
  .specs .item + .item{border-left:none;border-top:1px solid var(--line)}
  .hero h1{font-size:33px}
  .hero p.lead{font-size:18px}
  .page-head h1{font-size:29px}
  .page-head p{font-size:17px}
  .sec-head h2{font-size:27px}
  .grid-3,.grid-2,.shots{grid-template-columns:1fr}
  .nav-links{display:none}
  .nav-links.open{display:flex;position:absolute;top:68px;left:0;right:0;background:#fff;flex-direction:column;align-items:flex-start;gap:0;padding:8px 24px 18px;border-bottom:1px solid var(--line)}
  .nav-links.open a{padding:12px 0;width:100%}
  .nav-links.open .btn{width:100%;text-align:center;margin-top:8px}
  .menu-btn{display:flex}
  form .row{grid-template-columns:1fr}
  .band h2{font-size:26px}
  .cta-strip h2{font-size:25px}
  /* Touch targets. A 13px link in a 44px-tall row is the difference between a
     quote request and a mis-tap on a phone. */
  .hero .cta .btn,.page-head .cta .btn,.cta-strip .cta .btn{width:100%;text-align:center;padding:14px 20px}
  .hero .cta,.page-head .cta,.cta-strip .cta{gap:10px}
}

/* ============================================================
   PORTFOLIO — "Built for Engineers"
   ============================================================
   Built around four professional photographs that do not exist yet. See the
   long note at the top of tools/pages/portfolio.mjs. Three rules matter here.

   THE FRAME IS FIXED, THE PHOTOGRAPH IS NOT CROPPED. .pf-frame is a 4:3 box and
   the image inside it is object-fit:contain, not cover. cover would fill the box
   by throwing away whatever does not fit, which on a photograph shot to show a
   part's geometry is exactly the wrong half. contain letterboxes instead, onto
   the same soft grey the rest of the site uses, so a portrait, a square and a
   landscape shot all arrive whole and a photograph taken on a clean light
   background reads as though it were bled to the edges.

   THE LAYOUT FOLLOWS THE COUNT. data-count is written by the generator from the
   length of the items array. Four photographs make a two by two block, so each
   one is about 500px wide on desktop rather than 250px; three make a row of
   three; anything else falls back to auto-fit, which never leaves a dangling
   empty cell. Deleting the fourth item from PORTFOLIO reflows the row on its
   own and nothing here has to change.

   NOTHING SHIFTS WHEN THE PICTURE ARRIVES. The empty frame is an SVG at the same
   4:3, filling the same box, so swapping a photograph in moves no layout. */
.pf-grid{display:grid;gap:26px;grid-template-columns:repeat(auto-fit,minmax(min(100%,300px),1fr))}
.pf-grid[data-count="4"]{grid-template-columns:repeat(2,minmax(0,1fr))}
.pf-grid[data-count="3"]{grid-template-columns:repeat(3,minmax(0,1fr))}
.pf-grid[data-count="2"]{grid-template-columns:repeat(2,minmax(0,1fr))}
.pf-grid[data-count="1"]{grid-template-columns:minmax(0,1fr);max-width:720px}
.pf-card{background:#fff;border:1px solid var(--line);border-radius:14px;overflow:hidden;
  display:flex;flex-direction:column;color:inherit;box-shadow:0 1px 3px rgba(6,32,47,.05)}
a.pf-card{transition:.15s}
a.pf-card:hover{border-color:#bcd6e6;box-shadow:0 10px 28px rgba(6,32,47,.12);transform:translateY(-2px)}
.pf-frame{aspect-ratio:4/3;background:var(--soft);border-bottom:1px solid var(--line);
  position:relative;overflow:hidden}
.pf-frame picture,.pf-frame img,.pf-frame svg{display:block;width:100%;height:100%}
.pf-frame img{object-fit:contain}
.pf-frame.has-photo{background:var(--soft)}
.pf-ph{display:block}
.pf-body{padding:22px 24px 24px}
.pf-body h3{font-size:19px;margin:0}
.pf-slot{font-size:12px;text-transform:uppercase;letter-spacing:1.4px;color:var(--steel);
  font-weight:700;margin:8px 0 0}
.pf-meta{margin:14px 0 0;font-size:14.5px}
.pf-meta > div{display:grid;grid-template-columns:104px 1fr;gap:10px;padding:7px 0;
  border-top:1px solid var(--line)}
.pf-meta dt{color:var(--steel);font-weight:700;font-size:12px;text-transform:uppercase;
  letter-spacing:1px;padding-top:2px}
.pf-meta dd{margin:0;color:var(--muted)}
.pf-note{max-width:760px}

/* ---------- differentiators, the four reasons ---------- */
.why{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:22px}
.why .w{border-top:3px solid var(--steel);padding-top:18px}
.why .w h3{font-size:13px;text-transform:uppercase;letter-spacing:1.6px;color:var(--steel);
  margin:0 0 10px}
.why .w p{color:var(--muted);font-size:15px;margin:0}
.why .w p strong{color:var(--ink)}

/* ---------- prototype → production progression ---------- */
.flow{display:grid;grid-template-columns:1fr auto 1fr auto 1fr;align-items:stretch;gap:0}
.flow .f{background:#fff;border:1px solid var(--line);border-radius:12px;padding:24px}
.flow .f .k{font-size:11.5px;text-transform:uppercase;letter-spacing:1.6px;color:var(--steel);
  font-weight:700;margin-bottom:8px}
.flow .f h3{font-size:18px;margin:0 0 8px}
.flow .f p{color:var(--muted);font-size:14.5px;margin:0}
.flow .arrow{display:flex;align-items:center;justify-content:center;color:#8fc4e2;font-size:22px;
  padding:0 14px;font-weight:700}
.section-soft .flow .f{box-shadow:0 1px 3px rgba(6,32,47,.04)}

/* ---------- industries served ---------- */
.tiles{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:14px}
.tiles div{background:#fff;border:1px solid var(--line);border-radius:10px;padding:16px 18px;
  font-weight:600;font-size:15px;color:var(--navy)}

/* ---------- local service area ---------- */
.areas{display:flex;flex-wrap:wrap;gap:8px;margin-top:18px;padding:0;list-style:none}
.areas li{background:#e7f0f6;color:var(--steel);font-size:13.5px;font-weight:700;
  padding:6px 13px;border-radius:20px}

/* ---------- mobile quote bar ----------
   Instagram traffic lands on a phone, and the one thing that visitor has to be
   able to do is send a file. Below 640px a bar sits at the bottom of the window
   with the primary action in it, so "Get a Quote" is never more than one thumb
   away no matter how far down the page they have read.

   It defers to the privacy notice rather than fighting it. That notice is also
   fixed to the bottom, and two stacked bars on a 390px screen is most of the
   first thing a visitor sees. body.cookiebar-open is set by the notice's own
   script while it is showing and removed when it is dismissed, so this bar
   simply stands down until then. Nothing new is stored to make that work.

   quote.html and viewer.html never get one, because they are hand-maintained
   files that tools/layout.mjs does not generate, and a floating button pointing
   at the page you are already on is noise. A generated page can opt out the same
   way by setting bodyClass:'no-quotebar', which thank-you.html does. */
#quotebar{display:none}
@media (max-width:640px){
  #quotebar{display:block;position:fixed;left:0;right:0;bottom:0;z-index:800;
    background:rgba(255,255,255,.97);backdrop-filter:blur(6px);
    border-top:1px solid var(--line);box-shadow:0 -4px 20px rgba(6,32,47,.14);
    padding:10px 16px calc(10px + env(safe-area-inset-bottom,0px))}
  /* It arrives a moment late, on purpose, and this is not decoration.
     The privacy notice reveals itself 450ms after load and only then does its
     script put cookiebar-open on the body, so a bar painted at 0ms would appear,
     be covered, and disappear again in half a second. Coming in at 900ms means
     the notice has already claimed the bottom of the screen if it is going to,
     and this bar is simply never seen on that first paint. It also lets the
     hero have the screen to itself for the first moment, which is the one
     moment an ad visitor is deciding whether to stay.
     `both` holds the from-state before the delay elapses, so it does not flash
     at full opacity first. Anyone who has asked for less motion gets it
     immediately and without the slide. */
  #quotebar{animation:qbar .28s ease .9s both}
  @keyframes qbar{from{opacity:0;transform:translateY(100%)}to{opacity:1;transform:none}}
  @media (prefers-reduced-motion:reduce){ #quotebar{animation:none} }
  /* The bar and the privacy notice STACK; neither hides the other.
     This was display:none while the notice was up, which meant the bar was
     hidden from every FIRST-TIME visitor — cookiebar-open is set on arrival and
     removed only when the notice is dismissed — so the one feature built for
     cold ad traffic was switched off for exactly that traffic. Docking it under
     the header instead was worse: it covered the top of the hero at the moment
     the visitor is deciding whether to stay.
     So the bar keeps the bottom, where the thumb is, and the notice sits on top
     of it. The offset can be a constant because the BAR's height is fixed here
     (66px) while the notice's is not; measuring the notice would need script,
     and this needs none. */
  #quotebar{height:66px}
  body.cookiebar-open #cookiebar{bottom:calc(66px + env(safe-area-inset-bottom,0px))}
  #quotebar .inner{display:flex;gap:10px;align-items:center}
  #quotebar .btn{flex:1;text-align:center;padding:14px 12px;font-size:15px}
  #quotebar .sub{flex:0 0 auto;font-size:12px;color:var(--muted);line-height:1.3;max-width:104px}
  body.no-quotebar #quotebar{display:none}
  /* The bar covers the last stripe of the page, so give the document somewhere
     to end. Without this the footer's bottom line sits under it. */
  /* Room for the bar, and for the notice stacked above it while it is up. */
  body:not(.no-quotebar) footer{padding-bottom:96px}
  body:not(.no-quotebar).cookiebar-open footer{padding-bottom:200px}
}

/* ---------- responsive rules for the components above ----------
   These live HERE, after the components they modify, and not up in the two
   shared @media blocks with the rest of the site's breakpoints. That is not a
   filing preference, it is the cascade.

   The shared blocks sit further up this file than every component added in this
   revision. A media query carries no specificity of its own, so
   `@media (max-width:640px){ .pf-grid{...} }` written up there loses to a plain
   `.pf-grid{...}` written down here, on source order alone. It loses SILENTLY:
   the rule parses, the query matches, and the declaration is simply overridden.

   That is exactly what happened. Every override below was in the shared blocks
   first, and at 390px the portfolio still rendered two columns, the
   differentiators four, and the prototype-to-production row all five of its
   tracks, pushing the document 166px wider than the screen and scrolling every
   page sideways. Nothing about the CSS looked wrong; a browser measured it.

   If a component is added below this point, its breakpoints belong below it. */
@media (max-width:880px){
  /* Two photographs across, at every count. A 3-up row at 880px gives each one
     about 270px, which is a thumbnail; two gives it 420px. */
  .pf-grid,.pf-grid[data-count="2"],.pf-grid[data-count="3"],.pf-grid[data-count="4"]{grid-template-columns:repeat(2,minmax(0,1fr))}
  /* Three items in two columns would leave one cell empty, which is the exact
     thing a count-driven layout exists to prevent. The third photograph takes
     the whole row instead, and a wide frame suits it. */
  .pf-grid[data-count="3"] > :last-child{grid-column:1 / -1}
  .why{grid-template-columns:repeat(2,minmax(0,1fr))}
  /* The arrows are horizontal connectors and mean nothing in a column, so the
     column gets downward marks instead. minmax(0,1fr), never a bare 1fr: a bare
     1fr is minmax(auto,1fr), which floors the track at its own min-content
     width, and that is how a grid pushes a page sideways. */
  .flow{grid-template-columns:minmax(0,1fr);gap:12px}
  .flow .arrow{padding:0;transform:rotate(90deg);font-size:20px}
}
@media (max-width:640px){
  /* One photograph per row, at the full width of the container, which is the
     only size at which a part reads as a part. */
  .pf-grid,.pf-grid[data-count="2"],.pf-grid[data-count="3"],.pf-grid[data-count="4"]{grid-template-columns:minmax(0,1fr)}
  .pf-grid[data-count="3"] > :last-child{grid-column:auto}
  .why{grid-template-columns:minmax(0,1fr);gap:18px}
  .tiles{grid-template-columns:minmax(0,1fr)}
  .pf-meta > div{grid-template-columns:92px 1fr;gap:8px}
}

/* inline form result states — the customer never leaves the page to find out */
.formmsg{display:none;margin-top:14px;border-radius:10px;padding:13px 15px;font-size:14px;line-height:1.55}
.formmsg.show{display:block}
.formmsg.ok{background:#eaf7ef;border:1px solid #9fd5b4;color:#14663c}
.formmsg.ok strong{color:#0e5330}
.formmsg.err{background:#fdeceb;border:1px solid #f0b3ae;color:#8a2620}
.formmsg.err strong{color:#6f1c17}
.formmsg a{color:inherit;text-decoration:underline}

/* ---------- cookie / privacy notice ----------
   The site sets no cookies and runs no analytics, so this never has to block
   anything — it sits at the bottom, states the position, and gets out of the
   way. The only thing stored is the visitor's answer to it. */
#cookiebar{position:fixed;left:0;right:0;bottom:0;z-index:900;background:var(--navy);color:#d3e2ec;
  border-top:1px solid rgba(255,255,255,.14);box-shadow:0 -6px 28px rgba(6,32,47,.3);
  padding:16px 0 calc(16px + env(safe-area-inset-bottom,0px));transform:translateY(110%);
  transition:transform .3s cubic-bezier(.3,.9,.3,1);font-size:14px;line-height:1.55}
#cookiebar.show{transform:translateY(0)}
#cookiebar .inner{max-width:1080px;margin:0 auto;padding:0 24px;display:flex;align-items:center;gap:22px;flex-wrap:wrap}
#cookiebar p{margin:0;flex:1;min-width:260px}
#cookiebar strong{color:#fff}
#cookiebar a{color:#8fc4e2;text-decoration:underline}
#cookiebar .acts{display:flex;gap:10px;flex-wrap:wrap}
#cookiebar .btn{padding:10px 20px;font-size:14px;border-radius:8px;border:2px solid;cursor:pointer;font-weight:700;font-family:inherit}
#cookiebar .ok{background:#fff;border-color:#fff;color:var(--navy)}
#cookiebar .ok:hover{background:#e7f0f6}
#cookiebar .no{background:transparent;border-color:rgba(255,255,255,.5);color:#fff}
#cookiebar .no:hover{border-color:#fff;background:rgba(255,255,255,.08)}
@media (max-width:640px){
  /* At 390px the full notice took roughly two fifths of the first screen and
     sat over the primary call to action. Nothing here is legally required: the
     site sets no cookies. So the phone gets the true sentence and the link to
     the privacy page, where all of this is written out properly, and the detail
     appears from 641px up. */
  #cookiebar{padding:12px 0 calc(12px + env(safe-area-inset-bottom,0px));font-size:13.5px}
  #cookiebar .inner{gap:12px;padding:0 18px}
  #cookiebar .det{display:none}
  #cookiebar p{min-width:0}
  #cookiebar .acts{width:100%}
  #cookiebar .acts .btn{flex:1;padding:9px 18px}
}
