/* Copyright © 2026 Northstar Growth Partners LLC. All rights reserved. */
/*
 * Motion and media for the proof room.
 *
 * Founder note, 2026-09-13: "They lack creativity and imagery from their social
 * media and advertising campaigns. None have any motion or video clips either."
 *
 * The constraint this is written under is the 2026-09-02 ruling that a concept
 * which is merely prettier gives the owner nothing he can act on and invites him
 * to argue taste. So nothing here is decoration for its own sake. Every piece
 * does a job an owner can check:
 *
 *   the ambient hero    a dark page that does not move reads as unfinished or
 *                       broken on a phone; a slow light pass reads as alive
 *   the media slot      his own photograph or a ten second clip of his own crew,
 *                       which is the only imagery that proves anything
 *   the reveal          nothing is hidden by it; it is a settle, not a fade in,
 *                       and the page is complete with JavaScript switched off
 *   the counters        the credibility numbers are the most skimmed thing on
 *                       the page and counting draws the eye to them once
 *   the map             answers "do you come out to Brazil" without a sentence
 *   the reviews         fixes reviews_invisible, a fault we write to strangers
 *                       about and commit on seventeen of our own pages
 *
 * Everything is CSS transforms and opacity, no library, no external request, and
 * all of it stops dead under prefers-reduced-motion.
 */

/* ---------- 1. the hero comes alive without an asset ---------- */

.ns-amb {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
  z-index: 0;
}
.ns-amb::before {
  /* A slow pass of light, the way sun crosses a roof over an afternoon. */
  content: "";
  position: absolute;
  top: -60%;
  left: -40%;
  width: 80%;
  height: 220%;
  background: linear-gradient(100deg, transparent 0%, rgba(255, 255, 255, 0.055) 45%, rgba(255, 255, 255, 0.085) 50%, rgba(255, 255, 255, 0.055) 55%, transparent 100%);
  transform: translate3d(0, 0, 0) rotate(8deg);
  animation: ns-sweep 19s cubic-bezier(.45, 0, .55, 1) infinite;
  will-change: transform;
}
.ns-amb::after {
  /* Grain. Keeps a flat gradient from banding on a cheap phone screen. */
  content: "";
  position: absolute;
  inset: 0;
  opacity: .5;
  background-image: radial-gradient(rgba(255,255,255,.05) .5px, transparent .5px);
  background-size: 3px 3px;
}
@keyframes ns-sweep {
  0%   { transform: translate3d(-30%, 0, 0) rotate(8deg); }
  100% { transform: translate3d(210%, 0, 0) rotate(8deg); }
}
/* The hero's own children must sit above the ambient layer. */
.hero > .wrap, .hero > header, .hero > nav { position: relative; z-index: 1; }

/* ---------- 2. the media slot: his photograph, or his crew on video ---------- */

.ns-media {
  position: relative;
  margin: 18px 0 6px;
  border-radius: 14px;
  overflow: hidden;
  background: rgba(0, 0, 0, .25);
  aspect-ratio: 16 / 10;
  max-width: 100%;
}
.ns-media > img,
.ns-media > video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  max-width: 100%;
}
.ns-media figcaption,
.ns-media .ns-cap {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  margin: 0;
  padding: 26px 14px 12px;
  font: 600 14px/1.35 var(--body, system-ui, sans-serif);
  color: #fff;
  background: linear-gradient(to top, rgba(6, 10, 16, .82), transparent);
}
/* A still frame is the default. The clip is an upgrade, never a requirement. */
.ns-media[data-ns-video] video { opacity: 0; transition: opacity .5s ease; }
.ns-media[data-ns-video].ns-playing video { opacity: 1; }
.ns-media[data-ns-video] img { position: absolute; inset: 0; }

/* ---------- 3. the settle: reveal that never hides anything ---------- */
/* Without the ns-js class, which only the script sets, every rule below is off
 * and the page renders complete. A reader with JavaScript blocked loses the
 * animation and loses nothing else. */

.ns-js [data-ns-reveal] {
  opacity: 0;
  transform: translate3d(0, 14px, 0);
  transition: opacity .55s cubic-bezier(.2, .7, .3, 1), transform .55s cubic-bezier(.2, .7, .3, 1);
  transition-delay: var(--ns-d, 0ms);
}
.ns-js [data-ns-reveal].ns-in {
  opacity: 1;
  transform: none;
}

/* ---------- 4. the credibility numbers count once ---------- */

.ns-count { font-variant-numeric: tabular-nums; }

/* ---------- 5. the service area, drawn ---------- */

.ns-map {
  display: block;
  width: 100%;
  max-width: 520px;
  margin: 18px auto 4px;
  height: auto;
}
.ns-map .ns-ring {
  fill: none;
  stroke: var(--accent, #33455C);
  stroke-width: 2;
  opacity: .55;
  stroke-dasharray: 4 6;
}
.ns-js .ns-map .ns-ring { animation: ns-spin 44s linear infinite; transform-origin: 50% 50%; }
.ns-map .ns-hub { fill: var(--accent, #33455C); }
.ns-map .ns-town { fill: var(--ink, #101822); opacity: .82; }
.ns-map text { font: 600 11px/1 var(--body, system-ui, sans-serif); fill: var(--ink, #101822); }
.ns-js .ns-map [data-ns-pop] { opacity: 0; }
.ns-js .ns-map.ns-in [data-ns-pop] { animation: ns-pop .5s cubic-bezier(.2, .8, .3, 1) forwards; animation-delay: var(--ns-d, 0ms); }
@keyframes ns-spin { to { transform: rotate(360deg); } }
@keyframes ns-pop { from { opacity: 0; transform: translate3d(0, 6px, 0); } to { opacity: 1; transform: none; } }

/* ---------- 6. reviews, on the page, where they can be read ---------- */

.ns-reviews { display: grid; gap: 12px; margin: 16px 0; }
@media (min-width: 720px) { .ns-reviews { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); } }
.ns-review {
  background: var(--card, #fff);
  border: 1px solid var(--line, #E3E8EE);
  border-radius: 12px;
  padding: 14px 16px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.ns-stars { display: flex; gap: 2px; color: #E8A317; line-height: 1; }
.ns-stars svg { width: 15px; height: 15px; fill: currentColor; }
.ns-review blockquote { margin: 0; font: 400 15px/1.5 var(--body, system-ui, sans-serif); color: var(--ink, #101822); }
.ns-review cite { font: 600 13px/1.3 var(--body, system-ui, sans-serif); color: var(--muted, #5A6875); font-style: normal; }
.ns-rating {
  display: inline-flex; align-items: center; gap: 8px;
  font: 700 15px/1 var(--body, system-ui, sans-serif); color: var(--ink, #101822);
}
.ns-rating small { font-weight: 500; color: var(--muted, #5A6875); }

/* ---------- 7. before and after, dragged ---------- */

.ns-ba { position: relative; overflow: hidden; border-radius: 14px; touch-action: pan-y; user-select: none; }
.ns-ba img { display: block; width: 100%; height: auto; max-width: 100%; }
.ns-ba .ns-after { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; clip-path: inset(0 0 0 var(--ns-x, 50%)); }
.ns-ba .ns-handle {
  position: absolute; top: 0; bottom: 0; left: var(--ns-x, 50%);
  width: 2px; background: #fff; box-shadow: 0 0 0 1px rgba(0,0,0,.2);
  cursor: ew-resize;
}
.ns-ba .ns-handle::after {
  content: ""; position: absolute; top: 50%; left: 50%;
  width: 34px; height: 34px; margin: -17px 0 0 -17px; border-radius: 50%;
  background: #fff; box-shadow: 0 2px 10px rgba(0,0,0,.35);
}
.ns-ba .ns-lab {
  position: absolute; bottom: 10px; padding: 4px 9px; border-radius: 999px;
  font: 700 11px/1 var(--body, system-ui, sans-serif); letter-spacing: .04em; text-transform: uppercase;
  background: rgba(6,10,16,.72); color: #fff;
}
.ns-ba .ns-lab.l { left: 10px; } .ns-ba .ns-lab.r { right: 10px; }
.ns-ba:focus-within .ns-handle::after { outline: 3px solid var(--accent, #33455C); outline-offset: 2px; }

/* ---------- 8. the presence he already has ---------- */

.ns-social { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin: 14px 0; }
.ns-social a {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 9px 14px; min-height: 44px; box-sizing: border-box;
  border: 1px solid var(--line, #E3E8EE); border-radius: 999px;
  font: 600 14px/1 var(--body, system-ui, sans-serif); text-decoration: none; color: var(--ink, #101822);
  transition: transform .18s ease, border-color .18s ease;
}
.ns-social a:hover { transform: translateY(-1px); border-color: var(--accent, #33455C); }
.ns-social svg { width: 17px; height: 17px; fill: currentColor; }

/* ---------- 9. nobody is made motion sick by a roofing website ---------- */

@media (prefers-reduced-motion: reduce) {
  .ns-amb::before { animation: none; opacity: .35; }
  .ns-js [data-ns-reveal] { opacity: 1 !important; transform: none !important; transition: none !important; }
  .ns-js .ns-map .ns-ring { animation: none; }
  .ns-js .ns-map [data-ns-pop] { opacity: 1 !important; animation: none !important; }
  .ns-social a { transition: none; }
  .ns-media[data-ns-video] video { display: none; }
}

































/* ---------- 10. his own photographs, 2026-09-13 ---------- */
/*
 * Every picture below came off that owner's own website. Nothing is stock and
 * nothing is invented. The hero scrim is baked into the file at build time and
 * measured: white type clears 7 to 1 across the text column at the 97th
 * percentile of brightness, so the headline cannot be lost in a bright sky.
 */
.hero.ns-has-photo { background-color: var(--deep); }
.ns-hero-photo {
  position: absolute; inset: 0; overflow: hidden; z-index: 0; pointer-events: none;
}
.ns-hero-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Founder, 2026-09-14, night: the dark shading goes. The page's ambient
 * gradient and grain no longer sit over a photograph; the same band the
 * engine draws behind a headline (.hero-scrim) does the work, left to right. */
.hero.ns-has-photo::before, .hero.ns-has-photo::after { display: none; }
.ns-hero-photo::after { content: ""; position: absolute; inset: 0; background:
  linear-gradient(90deg, rgba(8,15,22,.84) 0%, rgba(8,15,22,.70) 34%, rgba(8,15,22,.34) 62%, rgba(8,15,22,.10) 100%),
  linear-gradient(180deg, rgba(8,15,22,.16), transparent 20%, transparent 70%, rgba(8,15,22,.42)); }
.hero.ns-has-photo .ns-amb { display: none; }
.hero.ns-has-photo h1, .hero.ns-has-photo .lede { text-shadow: 0 1px 2px rgba(0,0,0,.35); }
.hero.ns-has-photo > .wrap,
.hero.ns-has-photo > header,
.hero.ns-has-photo > nav { position: relative; z-index: 3; }

.ns-gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 232px), 1fr));
  gap: 12px;
  margin: 14px 0 4px;
}
.ns-gallery figure {
  margin: 0; border-radius: 12px; overflow: hidden;
  background: var(--card); border: 1px solid var(--line);
  aspect-ratio: 19 / 13;
}
.ns-gallery img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  transition: transform .5s cubic-bezier(.2, .7, .3, 1);
}
.ns-gallery figure:hover img { transform: scale(1.035); }

.ns-feature {
  margin: 16px 0 6px; border-radius: 14px; overflow: hidden;
  border: 1px solid var(--line); max-width: 100%;
}
.ns-feature img { display: block; width: 100%; height: auto; }

@media (prefers-reduced-motion: reduce) {
  .ns-gallery img { transition: none; }
  .ns-gallery figure:hover img { transform: none; }
}
/* ---------- 11. type over a photograph ---------- */
/*
 * The baked scrim guarantees the text column. Two things it cannot guarantee:
 * a narrow phone, where object-fit crops to the middle of the frame and the
 * plateau may not be what is on screen, and the tinted payoff clause, which
 * was set for a flat ground. Both are handled here rather than by darkening
 * the picture further and losing the thing we went and fetched.
 */
.hero.ns-has-photo h1,
.hero.ns-has-photo .lede,
.hero.ns-has-photo .chip { text-shadow: 0 1px 16px rgba(4, 8, 14, .55); }
.hero.ns-has-photo h1 em { color: #D7DEE7; }
.hero.ns-has-photo .chip { background: rgba(8, 14, 22, .38); }

@media (max-width: 760px) {
  .hero.ns-has-photo .ns-hero-photo::after {
    content: "";
    position: absolute; inset: 0;
    background: linear-gradient(to bottom,
      color-mix(in srgb, var(--deep) 72%, transparent) 0%,
      color-mix(in srgb, var(--deep) 58%, transparent) 55%,
      color-mix(in srgb, var(--deep) 78%, transparent) 100%);
  }
}

/* ---------- 12. the elaboration pass, 2026-09-13 ---------- */
/*
 * Founder note the same evening: "Our site is to appear and embody an upgrade,
 * not merely a placeholder. We aren't designing plain post cards."
 *
 * Seven concepts were carrying eight images with between fourteen and seventy
 * two more already harvested off that owner's own website and sitting unused.
 * These are the blocks that spend them. Everything below reads the concept's
 * own tokens, so it belongs to that business and not to Northstar, and every
 * block degrades to a plain stack on a narrow screen.
 */

/* a full bleed band of one photograph, with a line of the page's own words */
.ns-band-photo {
  position: relative;
  margin: 26px 0;
  border-radius: 16px;
  overflow: hidden;
  min-height: clamp(220px, 34vw, 360px);
  display: flex;
  align-items: flex-end;
  background: var(--deep, #101C29);
}
.ns-band-photo > img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
}
.ns-band-photo::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(to top,
    color-mix(in srgb, var(--deep, #101C29) 88%, transparent) 0%,
    color-mix(in srgb, var(--deep, #101C29) 34%, transparent) 52%,
    color-mix(in srgb, var(--deep, #101C29) 12%, transparent) 100%);
}
.ns-band-photo p {
  position: relative; z-index: 1;
  margin: 0; padding: 20px 22px;
  color: #fff;
  font: 600 clamp(16px, 2.2vw, 21px)/1.35 var(--body, system-ui, sans-serif);
  text-shadow: 0 1px 14px rgba(4, 8, 14, .5);
  max-width: 30ch;
}

/* a photograph beside its paragraph, alternating sides down the page */
.ns-split {
  display: grid;
  grid-template-columns: 1fr;
  gap: 16px;
  align-items: center;
  margin: 24px 0;
}
@media (min-width: 760px) {
  .ns-split { grid-template-columns: 1.05fr 1fr; gap: 26px; }
  .ns-split.ns-rev > figure { order: 2; }
}
.ns-split > figure {
  margin: 0;
  border-radius: 14px;
  overflow: hidden;
  border: 1px solid var(--line, #E3E8EE);
  aspect-ratio: 4 / 3;
}
.ns-split > figure img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ns-split h3 {
  margin: 0 0 8px;
  font: var(--dw, 400) clamp(19px, 2.4vw, 25px)/1.15 var(--display, system-ui, sans-serif);
  text-transform: uppercase; letter-spacing: -.2px;
}
.ns-split p { margin: 0; font-size: 15.5px; line-height: 1.55; color: var(--ink, #101822); }

/* service tiles that carry a picture of the service */
.ns-tiles {
  display: grid;
  grid-template-columns: 1fr;
  gap: 14px;
  margin: 18px 0 6px;
}
@media (min-width: 560px) { .ns-tiles { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 940px) { .ns-tiles { grid-template-columns: repeat(3, 1fr); } }
.ns-tile {
  display: flex; flex-direction: column;
  border: 1px solid var(--line, #E3E8EE);
  border-radius: 14px;
  overflow: hidden;
  background: var(--card, #fff);
  text-decoration: none;
  color: inherit;
  transition: transform .18s ease, border-color .18s ease;
}
.ns-tile:hover { transform: translateY(-2px); border-color: var(--signal, #33455C); }
.ns-tile:focus-visible { outline: 3px solid var(--signal, #33455C); outline-offset: 2px; }
.ns-tile > img { width: 100%; aspect-ratio: 16 / 10; object-fit: cover; display: block; }
.ns-tile > div { padding: 13px 15px 15px; }
.ns-tile b { display: block; font: 700 16px/1.25 var(--body, system-ui, sans-serif); margin-bottom: 5px; }
.ns-tile span { display: block; font: 400 14px/1.5 var(--body, system-ui, sans-serif); color: var(--muted, #5A6875); }

/* the badges an owner has actually earned, laid in a row */
.ns-strip {
  display: flex; flex-wrap: wrap; align-items: center; gap: 14px 22px;
  margin: 18px 0 4px; padding: 15px 16px;
  border: 1px solid var(--line, #E3E8EE);
  border-radius: 14px;
  background: var(--card, #fff);
}
.ns-strip img { height: 44px; width: auto; max-width: 150px; object-fit: contain; display: block; }
.ns-strip .ns-strip-label {
  font: 600 11.5px/1 var(--body, system-ui, sans-serif);
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--muted, #5A6875);
  width: 100%;
}

@media (prefers-reduced-motion: reduce) {
  .ns-tile { transition: none; }
  .ns-tile:hover { transform: none; }
}

/* ---------------------------------------------------------------- 13
 * The public record.
 *
 * 2026-09-14. The owner's reviews and his Better Business Bureau standing are
 * the strongest thing he has to sell with, and almost none of these companies
 * put either on their own site. This block does, with a link on every figure
 * and every quote so a reader can check it in one tap. That checkability is the
 * design: an unlinked five star rating on a contractor's website is wallpaper.
 */
.ns-proof { }
.ns-pf-chips {
  display: flex; flex-wrap: wrap; gap: 10px; margin: 18px 0 0;
}
.ns-pf-chip {
  display: flex; flex-direction: column; gap: 3px;
  padding: 13px 17px; min-height: 44px;
  border: 1px solid var(--line, #E3E8EE);
  border-left: 4px solid var(--signal, #33455C);
  border-radius: 10px;
  background: var(--card, #fff);
  text-decoration: none; color: inherit;
  transition: border-color .15s ease, transform .15s ease;
}
.ns-pf-chip:hover { border-color: var(--signal, #33455C); transform: translateY(-1px); }
.ns-pf-chip:focus-visible { outline: 3px solid var(--signal, #33455C); outline-offset: 3px; }
.ns-pf-chip b {
  font: 800 clamp(20px, 3.4vw, 27px)/1 var(--display, system-ui, sans-serif);
  color: var(--ink, #101822); font-variant-numeric: tabular-nums;
}
.ns-pf-chip span { font-size: 13px; color: var(--muted, #5A6875); }

.ns-pf-quotes {
  display: grid; gap: 14px; margin-top: 20px;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}
.ns-pf-q { margin: 0; display: flex; flex-direction: column; }
.ns-pf-q blockquote {
  margin: 0; flex: 1 1 auto;
  padding: 17px 18px;
  background: var(--card, #fff);
  border: 1px solid var(--line, #E3E8EE);
  border-radius: 12px 12px 12px 3px;
}
.ns-pf-q blockquote p { margin: 0; font: 400 15.5px/1.55 var(--body, system-ui, sans-serif); }
.ns-pf-q figcaption {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 7px;
  padding: 9px 4px 0; font-size: 13px; color: var(--muted, #5A6875);
}
.ns-pf-q figcaption a { color: var(--ink, #101822); }
.ns-pf-when { font-size: 11.5px; opacity: .72; }

@media (max-width: 620px) {
  .ns-pf-chip { flex: 1 1 100%; }
}

/* ---------------------------------------------------------------- 14
 * The presence hub.
 *
 * 2026-09-14, founder: "We want these sites to be top notch conversion points
 * of their entire digital presence. The delta; the vertice point."
 *
 * Patriot runs five profiles that do not link to each other. Sherrill's has a
 * Google page holding 250 reviews and a Facebook page, and neither knows the
 * other exists. Scattered presence does not compound. Gathered, it does.
 * Deliberately plain: this is a set of doors, not a row of brand badges.
 */
.ns-pr-links { display: flex; flex-wrap: wrap; gap: 9px; margin-top: 16px; }
.ns-pr-link {
  display: inline-flex; align-items: center; min-height: 44px;
  padding: 12px 16px;
  border: 1px solid var(--line, #E3E8EE);
  border-radius: 999px;
  background: var(--card, #fff);
  color: var(--ink, #101822);
  font: 600 14px/1 var(--body, system-ui, sans-serif);
  text-decoration: none;
  transition: border-color .15s ease, transform .15s ease;
}
.ns-pr-link:hover { border-color: var(--signal, #33455C); transform: translateY(-1px); }
.ns-pr-link:focus-visible { outline: 3px solid var(--signal, #33455C); outline-offset: 3px; }
.ns-pr-from { display: block; margin-top: 6px; font-size: 12.5px; color: var(--muted, #5A6875); }

/* An award is a fact with a year and a source, so it is set like one. */
.ns-awards { display: grid; gap: 10px; margin-top: 16px; }
.ns-award {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px;
  padding: 14px 16px;
  border: 1px solid var(--line, #E3E8EE);
  border-left: 4px solid var(--accent, var(--signal, #33455C));
  border-radius: 10px;
  background: var(--card, #fff);
}
.ns-award b { font: 700 15.5px/1.35 var(--body, system-ui, sans-serif); }
.ns-award span { font-size: 13.5px; color: var(--muted, #5A6875); }
.ns-award a { font-size: 13px; color: var(--ink, #101822); }

/* ---------------------------------------------------------------- 15
 * The owner's own section on the about page.
 *
 * 2026-09-14, founder: "the owner gets their own professional vanity section."
 * Vanity is the right word and it is earned, so it is set like a citation and
 * not like a boast: a name at display size, a role, one sentence, and then the
 * facts with the public record they came from hanging off each one. The link is
 * the whole point. An unlinked claim about a man is a rumour about him.
 */
.ns-owner {
  border-left: 4px solid var(--accent, var(--signal, #33455C));
  padding-left: clamp(16px, 3vw, 28px);
}
.ns-owner h2 {
  font: 800 clamp(24px, 4.6vw, 38px)/1.08 var(--display, system-ui, sans-serif);
  margin-bottom: 2px;
}
.ns-ow-role {
  margin: 0 0 12px;
  font: 600 12.5px/1 var(--body, system-ui, sans-serif);
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--muted, #5A6875);
}
.ns-ow-facts { list-style: none; margin: 18px 0 0; padding: 0; display: grid; gap: 12px; }
.ns-ow-facts li {
  padding: 14px 16px;
  background: var(--card, #fff);
  border: 1px solid var(--line, #E3E8EE);
  border-radius: 10px;
}
.ns-ow-facts li span { display: block; font: 400 15.5px/1.5 var(--body, system-ui, sans-serif); }
.ns-ow-facts li a {
  display: inline-block; margin-top: 7px;
  font-size: 12.5px; color: var(--muted, #5A6875);
}
.ns-ow-facts li a:hover { color: var(--ink, #101822); }
.ns-ow-note { margin-top: 12px; font-size: 12.5px; color: var(--muted, #5A6875); }

/* ---------------------------------------------------------------- 16
 * Motion, carousels, fades.
 *
 * 2026-09-14, founder: "Motion, video, Carousels" / "fade ins, fade outs".
 *
 * Everything below is behind .ns-motion, which _motion.js only adds when the
 * reader has NOT asked for reduced motion. So the default, with no script and
 * no permission, is a completely still page that works. Motion is the addition,
 * never the requirement, and the carousel is a scroll snap strip underneath, so
 * with the script off it is still swipeable and every picture is still there.
 */
.ns-gallery { scroll-behavior: smooth; }

.ns-car { position: relative; }
.ns-car-track {
  display: flex !important;
  gap: 12px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-padding: 0;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  padding-bottom: 2px;
}
.ns-car-track::-webkit-scrollbar { display: none; }
.ns-car-track > * {
  flex: 0 0 min(430px, 84%);
  scroll-snap-align: center;
  margin: 0;
}
.ns-car-track img {
  width: 100%; height: 100%;
  aspect-ratio: 19 / 13;
  object-fit: cover;
  border-radius: 12px;
  display: block;
}

.ns-car-nav {
  display: flex; align-items: center; justify-content: center; gap: 14px;
  margin-top: 14px;
}
.ns-car-btn {
  width: 46px; height: 46px; min-width: 46px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--line, #E3E8EE);
  border-radius: 50%;
  background: var(--card, #fff);
  color: var(--ink, #101822);
  font-size: 25px; line-height: 1; cursor: pointer;
  transition: border-color .15s ease, transform .15s ease;
}
.ns-car-btn:hover { border-color: var(--signal, #33455C); transform: translateY(-1px); }
.ns-car-btn:focus-visible { outline: 3px solid var(--signal, #33455C); outline-offset: 3px; }

.ns-car-dots { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; justify-content: center; }
.ns-car-dot {
  width: 22px; height: 22px; padding: 0;
  border: 0; background: transparent; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
}
.ns-car-dot::after {
  content: ''; width: 8px; height: 8px; border-radius: 50%;
  background: var(--line, #C9D2DC);
  transition: background .2s ease, transform .2s ease;
}
.ns-car-dot.on::after { background: var(--signal, #33455C); transform: scale(1.45); }
.ns-car-dot:focus-visible { outline: 2px solid var(--signal, #33455C); outline-offset: 1px; border-radius: 50%; }

/* The hero breathes. Twenty seconds out, twenty back, forever, and so slow
 * that nobody consciously sees it move. It is there to stop the first screen
 * reading as a photograph of a page. */
.ns-motion .ns-breathe {
  animation: ns-breathe 40s ease-in-out infinite alternate;
  transform-origin: 58% 52%;
  will-change: transform;
}
@keyframes ns-breathe {
  from { transform: scale(1); }
  to   { transform: scale(1.065); }
}

/* Quotes come through one at a time instead of stacking into a wall. The
 * moment anyone touches the block, every quote is handed back at once. */
.ns-q-fade { display: block !important; position: relative; }
.ns-q-fade > * {
  position: absolute; inset: 0;
  opacity: 0; visibility: hidden;
  /* Out first, then in. A symmetric crossfade puts both quotes on screen at
   * once for most of a second, and because they are stacked the two attribution
   * lines sit on the same baseline and ghost through each other. Found on the
   * Jake's concept, 2026-09-14. The outgoing one leaves in 380ms, the incoming
   * one waits 400ms before it starts, so they never share the frame. */
  transition: opacity .38s ease, visibility .38s;
}
.ns-q-fade > .on {
  opacity: 1; visibility: visible;
  transition: opacity .42s ease .4s, visibility 0s .4s;
}

@media (max-width: 620px) {
  .ns-car-track > * { flex-basis: 88%; }
}

@media (prefers-reduced-motion: reduce) {
  .ns-motion .ns-breathe { animation: none; }
  .ns-gallery, .ns-car-track { scroll-behavior: auto; }
  .ns-q-fade { display: grid !important; height: auto !important; }
  .ns-q-fade > * { position: static; opacity: 1; visibility: visible; transition: none; }
}

/* ---------------------------------------------------------------- 17
 * Who turns up.
 *
 * The single widest gap between what the national operators do on their LOCAL
 * pages and what local contractors do on their own: a row of faces with names.
 * One Hour names Darren at 28 years and Zach at 11; Benjamin Franklin names
 * Scott at 39. Their national home pages carry none of it, because it is a
 * local trust move and it only works local.
 *
 * Set as a plain grid of square portraits with a name under each. No hover
 * flourish, no role cards, no bios: the picture and the first name are the
 * whole payload, and anything more starts inventing things we were not told.
 */
.ns-mates {
  display: grid; gap: 14px; margin-top: 18px;
  grid-template-columns: repeat(auto-fill, minmax(118px, 1fr));
}
.ns-mate { margin: 0; }
.ns-mate img {
  width: 100%; aspect-ratio: 1 / 1; object-fit: cover;
  border-radius: 12px; display: block;
  background: var(--paper, #F6F8FB);
}
.ns-mate figcaption {
  margin-top: 8px; text-align: center;
  font: 600 13.5px/1.3 var(--body, system-ui, sans-serif);
  color: var(--ink, #101822);
}
@media (max-width: 620px) {
  .ns-mates { grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: 11px; }
  .ns-mate figcaption { font-size: 12.5px; }
}

/* ---------------------------------------------------------------- 18
 * Their own film, on their own page.
 *
 * A poster frame served from our own folder with a play button over it. The
 * YouTube iframe is built only when somebody clicks, and it points at
 * youtube-nocookie.com. An iframe on load would contact Google before the
 * reader asked for anything and cost half a megabyte of script for a video most
 * visitors never play. This costs one JPEG.
 */
.ns-films {
  display: grid; gap: 16px; margin-top: 18px;
  grid-template-columns: repeat(auto-fit, minmax(290px, 1fr));
}
.ns-film { margin: 0; }
.ns-film-go {
  position: relative; display: block; overflow: hidden;
  border-radius: 12px; background: #000; line-height: 0;
}
.ns-film-go img {
  width: 100%; aspect-ratio: 16 / 9; object-fit: cover; display: block;
  transition: transform .35s ease, opacity .2s ease;
}
.ns-film-go:hover img { transform: scale(1.035); opacity: .9; }
.ns-film-go:focus-visible { outline: 3px solid var(--signal, #33455C); outline-offset: 3px; }
.ns-film-play {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: 66px; height: 46px; border-radius: 11px;
  background: rgba(14, 20, 28, .74);
  border: 1px solid rgba(255, 255, 255, .28);
  transition: background .2s ease, transform .2s ease;
}
.ns-film-play::after {
  content: ''; position: absolute; left: 50%; top: 50%;
  transform: translate(-42%, -50%);
  border-style: solid; border-width: 9px 0 9px 15px;
  border-color: transparent transparent transparent #fff;
}
.ns-film-go:hover .ns-film-play { background: rgba(14, 20, 28, .88); transform: translate(-50%, -50%) scale(1.06); }
.ns-film figcaption { padding: 10px 2px 0; }
.ns-film figcaption b { display: block; font: 700 15px/1.35 var(--body, system-ui, sans-serif); }
.ns-film figcaption span { display: block; margin-top: 3px; font-size: 13px; color: var(--muted, #5A6875); }
.ns-film iframe { width: 100%; aspect-ratio: 16 / 9; border: 0; border-radius: 12px; display: block; }

/* ---------------------------------------------------------------- 19
 * The brag book, and the doors.
 *
 * 2026-09-14, founder: "a good website is like a vanity mirror for a proud
 * owner and his sales people, it's like a brag book for the customer" and
 * "It's a piece of art, not just a digital business card."
 *
 * Two sections, because he also said the platform buttons get their own area
 * and he is right: the record is proof, the doors are navigation, and mixing
 * them makes the proof look like link bait.
 *
 * The design decision that carries both: every entry is a link and every link
 * looks like one. A credential you cannot check is a boast. A credential with a
 * URL under it is a fact, and a wall of checkable facts is the only kind of
 * bragging that makes a stranger trust you more rather than less.
 */
.ns-bragbook h3 {
  margin: 26px 0 0;
  font: 600 12.5px/1 var(--body, system-ui, sans-serif);
  letter-spacing: .11em; text-transform: uppercase;
  color: var(--muted, #5A6875);
}
.ns-creds {
  display: grid; gap: 10px; margin-top: 12px;
  grid-template-columns: repeat(auto-fit, minmax(285px, 1fr));
}
.ns-cred {
  display: block; padding: 15px 17px;
  background: var(--card, #fff);
  border: 1px solid var(--line, #E3E8EE);
  border-left: 4px solid var(--signal, #33455C);
  border-radius: 10px;
  text-decoration: none; color: inherit;
  transition: border-color .15s ease, transform .15s ease, box-shadow .15s ease;
}
.ns-cred:hover {
  border-color: var(--signal, #33455C);
  transform: translateY(-1px);
  box-shadow: 0 6px 18px rgba(6, 12, 20, .07);
}
.ns-cred:focus-visible { outline: 3px solid var(--signal, #33455C); outline-offset: 3px; }
.ns-cred b { display: block; font: 700 15.5px/1.32 var(--body, system-ui, sans-serif); }
.ns-cred span { display: block; margin-top: 5px; font-size: 13.5px; line-height: 1.45; color: var(--muted, #5A6875); }

/* An award is the one thing on the page allowed to glint. */
.ns-creds-gold .ns-cred { border-left-color: var(--accent, #B8892B); }
.ns-creds-gold .ns-cred b { color: var(--ink, #101822); }

/* The doors. Matched buttons, our own glyphs, never anybody's trademark. */
.ns-plats {
  display: grid; gap: 10px; margin-top: 16px;
  grid-template-columns: repeat(auto-fill, minmax(158px, 1fr));
}
.ns-plat {
  position: relative;
  display: flex; align-items: center; gap: 11px;
  padding: 13px 15px; min-height: 56px;
  background: var(--card, #fff);
  border: 1px solid var(--line, #E3E8EE);
  border-radius: 12px;
  text-decoration: none; color: var(--ink, #101822);
  transition: border-color .15s ease, transform .15s ease, box-shadow .15s ease;
}
.ns-plat:hover {
  border-color: var(--signal, #33455C);
  transform: translateY(-2px);
  box-shadow: 0 8px 20px rgba(6, 12, 20, .09);
}
.ns-plat:focus-visible { outline: 3px solid var(--signal, #33455C); outline-offset: 3px; }
.ns-plat-g {
  flex: 0 0 auto; width: 32px; height: 32px; border-radius: 9px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--deep, #101C29);
  color: #fff;
}
.ns-plat-g svg { width: 19px; height: 19px; fill: currentColor; }
.ns-plat-n { font: 600 13.5px/1.25 var(--body, system-ui, sans-serif); }

@media (max-width: 620px) {
  .ns-creds { grid-template-columns: 1fr; }
  .ns-plats { grid-template-columns: repeat(auto-fill, minmax(142px, 1fr)); }
}

/* A real brand mark sits on its own brand colour. White marks on a very light
 * brand colour need a hairline or they vanish on a white card. */
.ns-plat-real { color: #fff; }
.ns-plat-real svg { width: 17px; height: 17px; fill: currentColor; }
.ns-plat-real { box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .08); }
/* The platform's own icon file (BBB, LinkedIn, Birdeye, HomeStars): shown as
 * it is, on no colour of ours. Founder, 2026-09-14: buttons match the brand. */
.ns-plat-img { background: transparent; box-shadow: none; overflow: hidden; }
.ns-plat-img img { width: 32px; height: 32px; display: block; border-radius: 9px; object-fit: cover; }
/* A wordmark (Nextdoor) is read, not glimpsed: it gets its width. */
.ns-plat-wide { width: 66px; border-radius: 9px; }
.ns-plat-wide svg { width: 56px; height: 14px; }
/* the wordmark already says the name; the label stays for screen readers */
.ns-plat-wide + .ns-plat-n { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
/* Type stands in only where no mark exists (Angi, Alignable, BuildZoom, a
 * rated but not accredited BBB): the platform's initial, or BBB's letters. */
.ns-plat-type { font: 800 12px/1 var(--body, system-ui, sans-serif); letter-spacing: .02em; color: #fff; }

/* ---------------------------------------------------------------- 20
 * When someone else wrote about them.
 *
 * The rarest thing on any of these pages and the only item nobody involved
 * wrote. A man can put anything on his own website; he cannot put himself in
 * the Tribune-Star. So it is set larger than the credentials and given room.
 */
.ns-presses { display: grid; gap: 12px; margin-top: 16px; }
.ns-press {
  display: block; padding: 18px 20px;
  background: var(--card, #fff);
  border: 1px solid var(--line, #E3E8EE);
  border-radius: 12px;
  text-decoration: none; color: inherit;
  transition: border-color .15s ease, transform .15s ease, box-shadow .15s ease;
}
.ns-press:hover {
  border-color: var(--signal, #33455C); transform: translateY(-1px);
  box-shadow: 0 8px 22px rgba(6, 12, 20, .08);
}
.ns-press:focus-visible { outline: 3px solid var(--signal, #33455C); outline-offset: 3px; }
.ns-press b {
  display: block;
  font: 700 clamp(16px, 2.1vw, 19px)/1.32 var(--body, system-ui, sans-serif);
}
.ns-press span {
  display: block; margin-top: 7px; font-size: 13px;
  letter-spacing: .04em; text-transform: uppercase;
  color: var(--muted, #5A6875);
}
/* every door button one line, so the row stays even */
.ns-plat-n { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* ---------------------------------------------------------------- 21
 * The owner's own sentence.
 *
 * Set larger than the facts under it and hung on a rule in the accent, because
 * it is the only thing in the section that is not a record. The records prove
 * he is real. This is the part that makes a stranger like him.
 */
.ns-ow-said { margin: 22px 0 26px; padding: 0 0 0 22px; border-left: 3px solid var(--accent, #FF6800); }
.ns-ow-said blockquote { margin: 0; }
.ns-ow-said blockquote p {
  margin: 0 0 10px;
  font: 400 clamp(18px, 2.6vw, 23px)/1.44 var(--display, Georgia, serif);
  text-wrap: pretty;
}
.ns-ow-said blockquote p:last-child { margin-bottom: 0; }
.ns-ow-said figcaption {
  margin-top: 12px; font-size: 13px; letter-spacing: .03em;
  color: var(--muted, #5A6875);
}
.ns-ow-said figcaption a { color: inherit; }

/* the sentence the reporter chose to print, on the card itself */
.ns-press-q {
  display: block; margin: 12px 0 0; quotes: none;
  font: 400 clamp(15px, 1.9vw, 17px)/1.5 var(--display, Georgia, serif);
  color: var(--ink, #10202F); max-width: 42em; text-wrap: pretty;
}
.ns-press-w {
  display: block; margin-top: 6px; font-style: normal;
  font-size: 13px; color: var(--muted, #5A6875);
}
.ns-press-w::before { content: "said\00a0"; }
/* hold the owner's own words to a readable measure */
.ns-ow-said blockquote p { max-width: 34em; }

/* ---------------------------------------------------------------------------
 * What we install. Founder, 2026-09-15: a manufacturer a business names on its
 * own site gets a mention here, and no mention goes on without the maker's own
 * mark beside it. The band is light on purpose: most of these marks are set in
 * black and would disappear on the dark one.
 * ------------------------------------------------------------------------ */
.ns-makers { background: var(--paper, #F3F5F7); border-radius: 14px; padding: 26px 24px 22px; }
.ns-makers-lede { margin: 0 0 18px; max-width: 62ch; color: var(--ink-soft, #4a5a67); }
.ns-makerlist { list-style: none; margin: 0; padding: 0; display: grid; gap: 18px;
  grid-template-columns: repeat(auto-fill, minmax(288px, 1fr)); }
.ns-maker { display: flex; align-items: flex-start; gap: 15px; }
.ns-maker-mark { flex: 0 0 96px; height: 52px; display: inline-flex; align-items: center;
  justify-content: flex-start; }
.ns-maker-mark img { max-width: 96px; max-height: 46px; width: auto; height: auto;
  object-fit: contain; display: block; }
.ns-maker-say { font-size: 14.5px; line-height: 1.5; color: var(--ink-soft, #4a5a67); }
.ns-maker-say b { display: block; color: var(--ink, #17222c); font-weight: 700; }
.ns-maker-what { display: block; font-style: normal; font-size: 13px; margin: 1px 0 4px;
  color: var(--muted, #6a7884); }
.ns-maker-say a { color: inherit; text-underline-offset: 3px; }
@media (max-width: 559px) {
  .ns-maker { gap: 12px; }
  .ns-maker-mark { flex-basis: 76px; height: 44px; }
  .ns-maker-mark img { max-width: 76px; max-height: 40px; }
}
