/* Vickerman storefront — Phase 1.
   Hand-written, no framework. Server-rendered pages with almost no CSS and no JS beat the
   current site on Core Web Vitals by default; a framework would give that lead away.
   This is functional scaffolding, not the final brand design. */

:root {
  --ink: #1c1c1c;
  --muted: #6a6a6a;
  --line: #e0ddd6;
  --bg: #fff;
  --accent: #1f4d3d;
  --accent-ink: #fff;
  --warn-bg: #fdf3d4;
  --warn-line: #d9b845;
  --radius: 3px;

  /* Cards and section bars, added 2026-08-02 for the home page work.

     SEPARATE FROM --radius on purpose. That one is on 61 things including inputs, badges and
     table corners, where 8px reads soft to the point of sloppy. A category card wants more
     rounding than a text field does, so the two are different questions and a theme should be
     able to answer them differently. */
  --radius-card: 8px;

  /* The band behind a section heading. A token rather than a literal so a theme can try the
     evergreen version without editing the stylesheet - see the note on .section-bar. */
  --band-bg: #f5f3ef;

  /* ASA's names for the same three tokens. Aliases rather than a rename: renaming --line,
     --accent and --ink would touch ~200 occurrences of STOREFRONT css to make an ADMIN
     stylesheet readable, and a mistyped variable fails silently - `var(--boarder)` resolves
     to initial, so you get a borderless element on a page nobody was testing.

     Three lines instead, and they buy the thing that actually matters: a rule can be moved
     between our admin.css and ASA's unchanged, so "why do these two files differ" is
     answerable with diff. */
  --border: var(--line);
  --brand:  var(--accent);
  --text:   var(--ink);
}

* { box-sizing: border-box; }

html { font-size: 16px; }

body {
  margin: 0;
  color: var(--ink);
  background: var(--bg);
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  line-height: 1.5;
}

img { max-width: 100%; height: auto; }
a { color: var(--accent); }

.wrap { max-width: 1280px; margin: 0 auto; padding: 0 1rem; }

/* --- test-environment banner ------------------------------------------- */
.env-banner {
  background: var(--warn-bg);
  border-bottom: 1px solid var(--warn-line);
  padding: .4rem 1rem;
  text-align: center;
  font-size: .85rem;
}

/* Deliberately louder than the env banner: mistaking a customer price for list is a
   costlier error than forgetting which host you are on. */
.viewas-banner {
  background: #1f3a5f;
  color: #fff;
  padding: .45rem 1rem;
  text-align: center;
  font-size: .85rem;
  display: flex;
  gap: .75rem;
  justify-content: center;
  align-items: baseline;
  flex-wrap: wrap;
}
.viewas-banner code { background: rgba(255, 255, 255, .15); padding: 0 .3rem; border-radius: 2px; }
.viewas-banner .btn-link { color: #fff; text-decoration: underline; background: none; border: 0; cursor: pointer; font: inherit; padding: 0; }
.viewas-stop { display: inline; }
.greeting { font-size: .9rem; margin-right: 1rem; white-space: nowrap; }

/* .sync-alert and .admin-nav a.current moved to admin.css. */

/* Sortable column headings. The whole heading is the target, so it behaves like the header
   cell it looks like rather than a word inside one. */
.input-tiny { width: 4.5rem; }
/* A ZIP+4 does not fit .input-tiny. It is kept in full and quoted against, so it has to be
   readable while it is being typed. */
.input-zip { width: 7.5rem; }
/* Checkout's shipping method and carrier pickers. .admin-form sizes text and textarea and stops
   there, so a select and a short code field need their own widths. */
.input-select { padding: .45rem .6rem; border: 1px solid var(--line); border-radius: var(--radius); font: inherit; max-width: 100%; }
.input-account { width: 12rem; }
.sort-link { color: inherit; text-decoration: none; white-space: nowrap; display: inline-block; }
.sort-link:hover { text-decoration: underline; }
.sort-link.current { font-weight: 700; }

/* Order line: thumbnail beside the description. The fixed 50px column keeps every row's
   text on the same left edge whether or not that SKU has a photograph. */
.line-item { display: flex; gap: .75rem; align-items: flex-start; }
.line-thumb {
  flex: 0 0 50px; width: 50px; height: 50px; object-fit: contain;
  border: 1px solid var(--line); border-radius: var(--radius); background: #fff;
}
.line-thumb-none { display: block; background: var(--bg-muted, #f5f5f5); }
.line-text { min-width: 0; }

/* --- header ------------------------------------------------------------- */
.site-header { border-bottom: 1px solid var(--line); }

.header-inner {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  padding-top: 1rem;
  padding-bottom: 1rem;
  flex-wrap: wrap;
}

.brand {
  font-size: 1.5rem;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--ink);
}

.search { display: flex; flex: 1 1 320px; min-width: 240px; }
.search input {
  flex: 1;
  padding: .5rem .75rem;
  border: 1px solid var(--line);
  border-radius: var(--radius) 0 0 var(--radius);
  font-size: 1rem;
}
.search button {
  padding: .5rem 1rem;
  border: 1px solid var(--accent);
  background: var(--accent);
  color: var(--accent-ink);
  border-radius: 0 var(--radius) var(--radius) 0;
  cursor: pointer;
}

.signin-note { color: var(--muted); font-size: .9rem; white-space: nowrap; }

/* --- navigation --------------------------------------------------------- */
.main-nav { border-top: 1px solid var(--line); background: #faf9f7; }
.nav-list { list-style: none; display: flex; gap: 2rem; margin: 0; padding: .5rem 0; flex-wrap: wrap; }
.nav-item { position: relative; }
.nav-item > a { font-weight: 600; text-decoration: none; }

/* Click-to-open dropdown. <details> carries the open/closed state itself, so there is no
   JavaScript here and none is wanted — see the no-third-party-scripts rule. */
.nav-drop > summary {
  font-weight: 600; cursor: pointer; list-style: none; user-select: none;
  padding: .1rem 0;
}
/* Both selectors are needed: WebKit uses the pseudo-element, everyone else uses
   list-style. Missing either leaves a stray disclosure triangle next to our own arrow. */
.nav-drop > summary::-webkit-details-marker { display: none; }
.nav-drop > summary::marker { content: ""; }
.nav-drop > summary::after {
  content: "▾"; font-size: .7em; margin-left: .35rem; vertical-align: middle; color: var(--muted);
}
.nav-drop[open] > summary::after { content: "▴"; }
.nav-drop > summary:hover { color: var(--accent); }

/* The panel floats above the page so opening a menu never pushes the page content down —
   that reflow is what makes a nav feel like it jumped out from under you. */
.nav-sub {
  list-style: none; margin: 0; padding: .5rem 0;
  position: absolute; z-index: 40; top: 100%; left: 0; min-width: 14rem;
  background: #fff; border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: 0 6px 20px rgb(0 0 0 / .10);
}
.nav-sub li { margin: 0; }
.nav-sub a {
  display: block; padding: .35rem 1rem;
  font-size: .9rem; color: var(--ink); text-decoration: none; white-space: nowrap;
}
.nav-sub a:hover { background: #faf9f7; color: var(--accent); }

/* Divides derived categories from hand-placed entries, so a calculator does not read as
   another product group. */
.nav-sep { border-top: 1px solid var(--line); margin: .35rem 0; }

/* The menu button. Hidden until nav-burger.js unhides it, and never shown on a wide screen —
   the bar has room there and a burger would only add a click. */
.nav-burger {
  display: none;
  align-items: center; gap: .5rem;
  width: 100%; padding: .6rem 0;
  background: none; border: 0; cursor: pointer;
  font: inherit; font-weight: 600; color: var(--ink); text-align: left;
}
/* Three bars drawn with gradient stops rather than three elements or an SVG file — same
   origin, no extra request, and it scales with the text beside it. */
/* The word beside the bars. Kept rather than dropped for an icon alone: three lines are a
   convention, not a label, and "Menu" costs one word and removes the guess. */
.nav-burger-text { font-size: .95rem; }
.nav-burger-bars {
  width: 1.15rem; height: .8rem; flex: none;
  background: linear-gradient(currentColor 0 0) 0 0 / 100% 2px no-repeat,
              linear-gradient(currentColor 0 0) 0 50% / 100% 2px no-repeat,
              linear-gradient(currentColor 0 0) 0 100% / 100% 2px no-repeat;
}
.main-nav.nav-open .nav-burger-bars {
  /* Two bars crossed. The middle one is dropped rather than animated — there is no motion
     here to respect prefers-reduced-motion for, and a static X reads the same. */
  background: linear-gradient(currentColor 0 0) 50% 50% / 100% 2px no-repeat,
              linear-gradient(currentColor 0 0) 50% 50% / 2px 100% no-repeat;
}

/* Narrow screens: the floating panel becomes an inline expander. A fixed-width dropdown
   pinned to the left of a 360px viewport would hang off the edge. */
@media (max-width: 700px) {
  .nav-list { gap: .25rem; flex-direction: column; }
  .nav-item { position: static; }
  .nav-sub {
    position: static; min-width: 0; border: 0; box-shadow: none;
    padding: .25rem 0 .5rem 1rem; background: transparent;
  }
  .nav-sub a { white-space: normal; padding: .3rem 0; }

  /* COLLAPSING IS GATED ON .js-collapsible, WHICH ONLY nav-burger.js ADDS. Without that gate
     a stylesheet cached ahead of a failed script would hide the whole menu with no button to
     bring it back — the rule cannot match until the thing that reverses it is running.

     Nine top-level entries plus an open dropdown filled the entire viewport of a phone before
     any page content appeared at all. */
  .main-nav.js-collapsible .nav-burger { display: flex; }
  .main-nav.js-collapsible .nav-list { display: none; }
  .main-nav.js-collapsible.nav-open .nav-list { display: flex; padding-bottom: .75rem; }

  /* Touch targets. .nav-sub a above is .3rem, which is fine for a mouse and small for a
     thumb; inside the collapsed menu these are the primary way around the site. */
  .main-nav.js-collapsible .nav-list > .nav-item > a,
  .main-nav.js-collapsible .nav-drop > summary { padding: .5rem 0; }
}

/* --- home carousel -------------------------------------------------------
   Scroll-snap, not JavaScript. Swipe works on touch, the scrollbar works on desktop, the
   numbered links jump.

   Auto-advance is off unless an interval is set at /admin/settings, and when it is on it
   comes with the pause control WCAG 2.2.2 requires. See carousel.js. */
.carousel { position: relative; margin-bottom: 1.5rem; }
.carousel-track {
  display: flex; overflow-x: auto; scroll-snap-type: x mandatory;
  scroll-behavior: smooth; gap: 0; border-radius: var(--radius);
}
.carousel-track::-webkit-scrollbar { height: 6px; }
.carousel-track::-webkit-scrollbar-thumb { background: var(--line); border-radius: 3px; }
.slide { position: relative; flex: 0 0 100%; scroll-snap-align: start; }
.slide img { display: block; width: 100%; height: auto; }
.slide-caption {
  position: absolute; left: 0; bottom: 0; right: 0;
  padding: 1.25rem 1.5rem; color: #fff;
  /* Gradient rather than a flat overlay: text stays readable over a light photograph
     without dimming the whole image. */
  background: linear-gradient(to top, rgb(0 0 0 / .70), rgb(0 0 0 / 0));
}
.slide-caption h2 { margin: 0 0 .25rem; font-size: 1.5rem; }
.slide-caption p { margin: 0 0 .6rem; font-size: .95rem; }
.carousel-dots { display: flex; gap: .4rem; justify-content: center; margin-top: .6rem; }
.carousel-dots a {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.7rem; height: 1.7rem; border: 1px solid var(--line); border-radius: 50%;
  font-size: .8rem; text-decoration: none; color: var(--muted);
}
.carousel-dots a:hover { border-color: var(--accent); color: var(--accent); }

/* The dot for the slide currently on screen. Set by carousel.js on click and on scroll, so a
   swipe updates it too. Colour AND a filled background - colour alone is not a distinction
   everybody can see. */
.carousel-dots a.current,
.carousel-dots a[aria-current] {
  border-color: var(--accent); background: var(--accent); color: var(--accent-ink);
}

/* Pause/play for the auto-advancing banner. Created by carousel.js, so it is only ever on the
   page when there is motion to stop.

   DELIBERATELY PLAIN AND NOT TUCKED AWAY. The temptation is a small icon floating over a
   corner of the artwork; a control that satisfies WCAG 2.2.2 has to be findable, and over a
   photograph there is no contrast ratio that holds for every slide. A word under the dots
   always reads. */
.carousel-pause {
  display: block; margin: .4rem auto 0; padding: .15rem .6rem;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: none; color: var(--muted); font: inherit; font-size: .8rem; cursor: pointer;
}
.carousel-pause:hover { border-color: var(--accent); color: var(--accent); }

/* Whole-slide link, used when a slide has a destination but no button text — artwork that
   already says "shop now" in the picture does not want the words repeated underneath.

   An empty stretched anchor rather than wrapping the slide: the caption stays outside it, so
   its text is still selectable and is not swallowed into the link's accessible name. It sits
   BELOW the caption in z-order so a button, if one is ever added back, still wins the click. */
.slide-hit { position: absolute; inset: 0; z-index: 1; }
.slide-caption { z-index: 2; }
.slide-hit:focus-visible { outline: 3px solid var(--accent); outline-offset: -3px; }

/* A caption over a small image would cover it; below 600px it sits under instead. */
@media (max-width: 600px) {
  .slide-caption { position: static; background: #1c1c1c; }
}

/* --- header logo --------------------------------------------------------- */
.brand-logo { display: block; max-height: 46px; width: auto; }

/* --- footer -------------------------------------------------------------- */
.footer-content { margin-bottom: .75rem; font-size: .9rem; }
.footer-content ul { list-style: none; padding: 0; margin: 0 0 .5rem;
                     display: flex; gap: 1.25rem; flex-wrap: wrap; }
.footer-content a { color: var(--ink); }

/* Marketing Hub form embed. Sized here rather than inline because CSP is style-src 'self'
   with no unsafe-inline — a style attribute on the iframe would be dropped and it would
   collapse to nothing. min-height so a postMessage resize that never arrives leaves the
   form a little tall instead of clipped. */
/* Embedded Marketing Hub form. Sizing is HERE and not an inline style on the iframe: the
   Content-Security-Policy is style-src 'self' with no unsafe-inline, so a style attribute
   would be dropped by the browser and the frame would collapse to nothing. min-height means
   a postMessage resize that never arrives leaves the form a little tall, not cut in half. */
.form-embed { margin: 1.5rem 0; }
/* 680px matches Marketing Hub's own tested default. Too tall beats clipped. */
.form-embed iframe { display: block; width: 100%; border: 0; min-height: 680px; }

/* --- classes content editors may use -------------------------------------
   Inline styles are stripped by the sanitiser on purpose, so these exist to give an editor
   real formatting without one. Documented on the content editor screen; keep that list and
   these rules in step. */

/* Opening paragraph, slightly larger. */
.lead { font-size: 1.1rem; color: var(--ink); }

/* Boxed aside for a note, a warning, or a shipping cut-off. */
.callout {
  border: 1px solid var(--line); border-left: 3px solid var(--accent);
  background: #faf9f7; padding: .8rem 1rem; border-radius: var(--radius); margin: 1rem 0;
}
.callout-warn { border-left-color: var(--warn-line); background: var(--warn-bg); }

/* Two and three column text. Collapses on narrow screens without the editor doing anything. */
/* NOTE: this is CSS multi-column — the content FLOWS across both columns and breaks wherever
   it happens to reach the bottom. Right for a long run of prose (the FAQ). WRONG for a
   sidebar beside a body: those are two separate things and would be treated as one stream,
   split mid-item. Use .sidebar-layout for that. */
.columns-2 { columns: 2; column-gap: 2.5rem; }
@media (max-width: 700px) { .columns-2 { columns: 1; } }

/* A narrow nav column beside the content — an "On this page" table of contents on a policy
   page. A real two-column GRID, so the two halves stay separate. */
.sidebar-layout { display: grid; grid-template-columns: 1fr 3fr; gap: 2.5rem; align-items: start; }
@media (max-width: 700px) { .sidebar-layout { grid-template-columns: 1fr; } }
.sidebar-layout nav ul { list-style: none; padding: 0; margin: 0; font-size: .9rem; line-height: 1.9; }

/* Featured block — brand green, white text. The alternative was an inline background-color
   on every page that wants one, which the sanitiser strips and which would not follow a
   later change of brand colour. */
.callout-accent {
  background: var(--accent); color: var(--accent-ink);
  padding: 1.25rem 1.5rem; border-radius: var(--radius); margin: 1rem 0;
}
.callout-accent h2, .callout-accent h3, .callout-accent p { color: var(--accent-ink); }
.callout-accent a { color: var(--accent-ink); }

/* Header block for a formal document — privacy policy, SMS terms. Wraps the title, an
   optional small-caps label, and the effective date. */
.page-banner {
  background: #f5f4f1; padding: 1.25rem 1.5rem;
  border-radius: var(--radius); margin-bottom: 1.5rem;
}
.page-banner > :first-child { margin-top: 0; }
.page-banner > :last-child { margin-bottom: 0; }
.page-banner .category-label {
  font-size: .75rem; font-weight: 700; letter-spacing: .1em;
  text-transform: uppercase; color: var(--muted); margin-bottom: .4rem;
}
.page-banner .page-meta { font-size: .875rem; color: var(--muted); margin-top: .4rem; }

/* A link that should read as a button. */
.button-link {
  display: inline-block; background: var(--accent); color: var(--accent-ink);
  padding: .55rem 1.4rem; border-radius: var(--radius); text-decoration: none;
  font-size: .9rem;
}
.button-link:hover { opacity: .92; }

/* Image floated beside text, clearing itself on mobile. */
.img-right { float: right; max-width: 45%; margin: 0 0 1rem 1.5rem; border-radius: var(--radius); }
.img-left  { float: left;  max-width: 45%; margin: 0 1.5rem 1rem 0; border-radius: var(--radius); }
@media (max-width: 700px) { .img-right, .img-left { float: none; max-width: 100%; margin: 0 0 1rem; } }

/* --- shared page furniture ---------------------------------------------- */
main { padding: 1.5rem 1rem 3rem; }
h1 { font-size: 1.75rem; margin: .5rem 0 1rem; }
h2 { font-size: 1.25rem; }

/* Category hero.

   THE SHAPE IS ENFORCED HERE, not left to the file. This was `height: auto`, which means the
   rendered height follows the IMAGE's aspect ratio — the width/height attributes on the tag
   only reserve space until it loads, then the real shape wins. UploadService fits an upload
   inside 1500x500 without cropping, so an off-shape picture is stored off-shape (a portrait
   becomes something like 750x500) and then `width: 100%` stretched it across the viewport:
   on a 1900px screen that is a 2.5x upscale and over 1200px tall. A banner filled the page.

   aspect-ratio pins it to the 3:1 the upload guidance asks for, object-fit: cover fills that
   box rather than letterboxing it with bars, and max-height stops a very wide screen turning
   the banner back into a wall. Cropping on DISPLAY is not the same as cropping on upload —
   the stored file still has everything in it, so re-cropping later loses nothing. */
.page-hero {
  display: block; width: 100%; max-width: 100%;
  aspect-ratio: 3 / 1;
  max-height: 20rem;
  object-fit: cover;
  object-position: center;
  background: #f2f0ec;   /* shows only for a moment, or behind a transparent PNG */
  border-radius: var(--radius); margin: 0 0 1rem;
}
/* Shorter on a phone: 3:1 of a narrow screen is a letterbox slot with nothing readable in it. */
@media (max-width: 700px) { .page-hero { aspect-ratio: 2 / 1; max-height: 12rem; } }

.breadcrumb { font-size: .85rem; color: var(--muted); margin-bottom: .5rem; }
.breadcrumb a { color: var(--muted); }
.breadcrumb span { margin: 0 .25rem; }

.count { color: var(--muted); font-size: .9rem; }

/* --- The bar above a result grid: count on the left, sort on the right ----
   .results-head was in Category.cshtml's markup with NO RULE ANYWHERE - the class the sort
   control needed already existed and had never done anything, so the count simply sat in a bare
   div. It is defined here now and the other two result pages use it, so all three carry the same
   bar instead of one having a count, one having a bare paragraph and Sale having neither.

   align-items: end rather than center - the count is a line of small text and the select is a
   control half again as tall, so centring leaves the text floating. Baselines read better. */
.results-head {
  display: flex;
  align-items: end;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: .75rem;
}
.results-head .count { margin: 0; }

/* margin-left: auto so the control stays right-aligned even on a page with no count beside it -
   the layout should not depend on the sibling being there. */
.sort-select {
  display: flex;
  align-items: center;
  gap: .5rem;
  margin-left: auto;
  font-size: .85rem;
}
.sort-select label { color: var(--muted); }
.sort-select select {
  padding: .35rem .5rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  font: inherit;
  background: #fff;
}
/* The button is removed by sort-select.js the moment it takes over, so this styles a control
   that is only ever seen with scripting off. It still has to look deliberate. */
.sort-select .btn-secondary { padding: .3rem .8rem; font-size: .85rem; }

@media (max-width: 640px) {
  /* The select takes the width it needs rather than the row wrapping to two lines for the sake
     of a four-word label. */
  .results-head { gap: .5rem; }
  .sort-select { width: 100%; margin-left: 0; }
  .sort-select select { flex: 1; }
}
.empty { color: var(--muted); padding: 2rem 0; }
.note { color: var(--muted); font-size: .85rem; }

/* `class="note warn"` is how 26 places across admin, cart and checkout render a warning or an
   error. It had NO RULE, so every one of them came out as muted grey helper text — a failed
   sales-tax quote read exactly like a hint. Amber and semibold, ASA's values.

   MUST stay after `.note`: equal specificity, so the later rule is what overrides the colour.
   MUST stay in site.css: Cart, Checkout, Account/Orders, Account/Projects and
   Account/QuickOrder all use it, and they never load admin.css.

   Carries its own font-size so `class="warn"` alone works. Admin uses the bare form, which is
   ASA's; the storefront still writes `class="note warn"` and renders identically, since .note
   sets the same size. */
.warn { color: #b45309; font-weight: 600; font-size: .85rem; }

/* --- search type-ahead ----------------------------------------------------
   Built by wwwroot/js/search-suggest.js; absent entirely without JavaScript. The form is
   position:relative so the panel hangs off the input rather than the page. */
form.search { position: relative; }
.suggest {
  position: absolute; top: 100%; left: 0; right: 0; z-index: 40;
  list-style: none; margin: .25rem 0 0; padding: .25rem 0;
  background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: 0 6px 20px rgba(0,0,0,.12); max-height: 22rem; overflow-y: auto;
}
.suggest-item a {
  display: flex; gap: .6rem; align-items: baseline;
  padding: .4rem .7rem; text-decoration: none; color: var(--ink); font-size: .875rem;
}
/* The item number is the thing being matched, so it leads and stays monospaced - a column of
   numbers is scannable only if the digits line up. */
.suggest-item-no {
  font-family: Consolas, "Courier New", monospace; font-size: .8rem;
  color: var(--muted); flex: 0 0 auto;
}
.suggest-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.suggest-item.on a, .suggest-item a:hover { background: #f2f0ec; }
.suggest-item.on .suggest-item-no { color: var(--ink); }

/* The same panel on the add-to-cart field, where picking a row FILLS the box rather than
   navigating - so the rows are not links and need their own padding and hover. The class is
   added at runtime by item-lookup.js, so nothing is positioned relative to a panel that will
   never exist. */
.item-lookup { position: relative; }
.item-lookup .suggest-item {
  display: flex; gap: .6rem; align-items: baseline;
  padding: .4rem .7rem; font-size: .875rem; cursor: pointer;
}
.item-lookup .suggest-item.on, .item-lookup .suggest-item:hover { background: #f2f0ec; }

/* .project-edit-block was here and is gone. It set the top rule and spacing for the standalone
   "Change this project" block, which became a collapsed panel and then, once the details TABLE at
   the top of the page became the editor, nothing at all. Nothing in any view carries the class.

   Every touched row is still marked - a staged edit that looks like a saved one is worse than no
   editing at all - and those rules follow. */
/* Left border rather than a background wash: it survives the row-striping and stays legible
   for a reader who cannot separate the colours. The word in the row says which is which —
   colour is never the only signal. */
.row-added td:first-child   { box-shadow: inset 3px 0 0 var(--accent); }
.row-changed td:first-child { box-shadow: inset 3px 0 0 #b45309; }
.row-removed td { opacity: .45; text-decoration: line-through; }
.row-removed td:first-child { box-shadow: inset 3px 0 0 #b3261e; text-decoration: none; }

/* --- editorial blocks on a category page ----------------------------------
   Typed blocks, one <section class="block block-{type}"> each, per
   database/02_schema_merchandising.sql: RichText | Comparison | FAQ | TileRow | Video.

   NOTE ON STATE: only RichText currently renders its content — Category.cshtml has a single
   `@if (block.BlockType == "RichText")` and the other four emit their heading and nothing
   else. The rules below are therefore groundwork: RichText and .intro take effect today, the
   rest land when the renderer emits them in Phase 1b. Written against the obvious markup for
   each so that work is styling-complete when it arrives.

   Measure is capped at 68ch. Editorial copy set to the full width of a 1200px page is
   genuinely harder to read, and these blocks exist to be read. The grid layouts (TileRow,
   Comparison) opt out, because they are not prose. */

.block { margin: 2rem 0; }
.block > h2 { font-size: 1.25rem; margin: 0 0 .75rem; }
.block > :last-child { margin-bottom: 0; }

/* Opening paragraph above the blocks - larger, and the same measure. */
.intro { max-width: 68ch; font-size: 1.05rem; line-height: 1.65; margin: 0 0 1.5rem; }
.intro > :last-child { margin-bottom: 0; }

/* Prose. Admin pastes sanitised HTML into these, so the rules have to cover whatever a
   marketing page legitimately contains rather than a fixed shape. */
.block-richtext { max-width: 68ch; line-height: 1.65; }
.block-richtext p { margin: 0 0 1rem; }
.block-richtext ul, .block-richtext ol { margin: 0 0 1rem; padding-left: 1.4rem; }
.block-richtext li { margin-bottom: .35rem; }
.block-richtext h3 { font-size: 1.05rem; margin: 1.5rem 0 .5rem; }
.block-richtext img { max-width: 100%; height: auto; border-radius: var(--radius); }
.block-richtext table { width: 100%; border-collapse: collapse; margin: 0 0 1rem; }
.block-richtext th, .block-richtext td {
  border-bottom: 1px solid var(--line); padding: .5rem .6rem; text-align: left;
}

/* Question and answer. Styled for both shapes an author might produce - a definition list,
   or heading/paragraph pairs - so neither renders as an undifferentiated wall. */
.block-faq { max-width: 68ch; line-height: 1.6; }
.block-faq dt, .block-faq h3 { font-weight: 600; margin: 1.25rem 0 .3rem; font-size: 1rem; }
.block-faq dt:first-child, .block-faq h3:first-of-type { margin-top: 0; }
.block-faq dd, .block-faq h3 + p { margin: 0 0 1rem; color: var(--ink); }

/* Side-by-side comparison. A table on desktop; on a narrow screen it scrolls inside its own
   box rather than forcing the whole page sideways. */
.block-comparison { overflow-x: auto; }
.block-comparison table { width: 100%; min-width: 34rem; border-collapse: collapse; font-size: .9rem; }
.block-comparison th, .block-comparison td {
  border: 1px solid var(--line); padding: .5rem .7rem; text-align: left; vertical-align: top;
}
.block-comparison thead th {
  background: #faf9f7; font-size: .78rem; text-transform: uppercase; letter-spacing: .04em;
}
.block-comparison tbody th { font-weight: 600; background: #faf9f7; }

/* Add .with-logos when the first thing in each cell is a picture — a showroom logo, a partner
   mark, a certification badge.

   THE PROBLEM IT SOLVES IS NOT ALIGNMENT OF THE CELLS. Those are already vertical-align: top
   above. It is that logos arrive at whatever aspect ratio their designer drew them: four
   images at width="140" come out four different HEIGHTS, so each column's address starts at a
   different point down the page and the row reads as broken even though the markup is right.

   Every logo therefore gets an identical box and is fitted inside it. `contain`, never
   `cover` — a wordmark that is cropped to fill a square is worse than one that is small.
   object-position centres it, so a wide logo and a tall one both sit on the same baseline. */
.block-comparison.with-logos td > img:first-child {
  display: block;
  width: auto; max-width: 100%;
  height: 6.5rem; object-fit: contain; object-position: center bottom;
  margin: 0 auto .75rem;
}

/* A cell whose image is missing collapses to nothing and re-staggers the row - which is
   exactly what a not-yet-found photograph does. Reserving the space keeps the other three
   aligned while it is being tracked down. */
.block-comparison.with-logos td { min-height: 6.5rem; }

/* A row of promoted tiles. auto-fit rather than a fixed count, so three tiles and five tiles
   both look deliberate and neither needs a per-block setting. */
.block-tilerow ul { list-style: none; margin: 0; padding: 0; }
.block-tilerow ul, .block-tilerow .tiles {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1.25rem;
}
.block-tilerow img { width: 100%; height: auto; display: block;
  border: 1px solid var(--line); border-radius: var(--radius); }
.block-tilerow a { text-decoration: none; }
.block-tilerow h3 { font-size: .95rem; margin: .5rem 0 .25rem; }
.block-tilerow p { font-size: .85rem; color: var(--muted); margin: 0; }

/* Add .with-icons when the pictures are MARKS rather than artwork — social platform logos,
   payment badges, a certification seal.

   The rule above is built for tile artwork: width 100% with a border, which is right for a
   photograph filling a tile and wrong for a 48px glyph, where it stretches the mark to the
   full column and draws a box round it. This sizes by HEIGHT instead, so a square Instagram
   glyph and a wide wordmark end up optically the same size.

   NO filter, NO mix-blend-mode, NO opacity. It is tempting to force a set of mismatched
   brand marks to one colour, and every major platform's brand guidelines forbid exactly that
   - Instagram and TikTok both prohibit recolouring or altering the mark. Whatever file is
   supplied is rendered as supplied. If they need to match, that is a decision to make in the
   artwork, not in CSS.

   object-fit: contain rather than a fixed width, so a mark is never squashed to fit. */
.block-tilerow.with-icons img {
  width: auto; height: 2.5rem; max-width: 100%;
  border: 0; border-radius: 0;
  object-fit: contain; object-position: left center;
  margin: 0 0 .6rem;
}

/* Tiles become equal-height columns so the buttons land on one line. Without it each tile is
   as tall as its own copy and four buttons sit at four different heights - the same staggering
   the showroom logos caused, from a different direction. */
.block-tilerow.with-icons li { display: flex; flex-direction: column; align-items: flex-start; }
.block-tilerow.with-icons li > :last-child { margin-top: auto; padding-top: .75rem; }
.block-tilerow.with-icons p { margin: 0 0 .4rem; }

/* Video. aspect-ratio keeps the frame 16:9 without the padding-top hack, and caps the width
   so a video does not dominate a page that is mostly copy. */
.block-video { max-width: 46rem; }
.block-video iframe, .block-video video {
  display: block; width: 100%; aspect-ratio: 16 / 9; height: auto; border: 0;
  border-radius: var(--radius); background: #000;
}

/* --- faceted browse layout ----------------------------------------------- */
.browse { display: grid; grid-template-columns: 220px 1fr; gap: 2rem; align-items: start; }
@media (max-width: 800px) { .browse { grid-template-columns: 1fr; } }

.facets { font-size: .875rem; }
.facet { border-bottom: 1px solid var(--line); padding: .75rem 0; }
.facet h3 { font-size: .8rem; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); margin: 0 0 .5rem; }

/* Each group is a <details> so it can be collapsed; facet-collapse.js closes them below the
   800px breakpoint above. The chevron shows on every size even though only narrow screens
   auto-collapse - a control that looks like plain text is a hidden affordance, and somebody
   on a laptop who wants Light Color out of the way should be able to see that they can. */
.facet-group > summary {
  display: flex; align-items: center; justify-content: space-between; gap: .5rem;
  cursor: pointer; margin: 0 0 .5rem;
  list-style: none;                       /* Firefox marker */
}
.facet-group > summary::-webkit-details-marker { display: none; }
.facet-group > summary h3 { margin: 0; }
.facet-group:not([open]) > summary { margin-bottom: 0; }
.facet-group > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Chevron drawn from two borders rather than a glyph: no font dependency, and it inherits
   colour. Down when closed, up when open. */
.facet-group > summary::after {
  content: ""; flex: 0 0 auto; width: .4rem; height: .4rem; margin-bottom: .15rem;
  border-right: 1.5px solid var(--muted); border-bottom: 1.5px solid var(--muted);
  transform: rotate(45deg);
}
.facet-group[open] > summary::after { transform: rotate(-135deg); margin-bottom: -.15rem; }
.facet ul { list-style: none; margin: 0; padding: 0; }
.facet li a {
  display: flex; align-items: center; gap: .5rem;
  padding: .2rem 0; text-decoration: none; color: var(--ink);
}
.facet li a:hover .facet-label { text-decoration: underline; }
.facet-box {
  width: 14px; height: 14px; flex: 0 0 14px;
  border: 1px solid var(--line); border-radius: 2px; background: #fff;
  font-size: 10px; line-height: 13px; text-align: center; color: var(--accent);
}
/* \2713 = check mark. Drawn here rather than in the markup so Razor cannot
   HTML-encode it, and written as a CSS escape so the stylesheet stays ASCII. */
.facet li a.selected .facet-box { border-color: var(--accent); }
.facet li a.selected .facet-box::after { content: "\2713"; }
.facet li a.selected .facet-label { font-weight: 600; }
.facet-label { flex: 1; }
.facet-count { color: var(--muted); font-size: .75rem; }

/* Overflow values stay in the DOM (so counts are honest) but are hidden until the
   details toggle is opened. */
.facet-extra { display: none; }
.facet:has(.facet-more[open]) .facet-extra { display: block; }
.facet-more { margin-top: .35rem; }
.facet-more summary { cursor: pointer; color: var(--accent); font-size: .8rem; }

.facet-active { padding: .75rem 0; border-bottom: 1px solid var(--line); }
.facet-active-head { display: flex; justify-content: space-between; align-items: baseline; }
.facet-active-head span { font-size: .8rem; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); }
.facet-clear { font-size: .78rem; }
.facet-chips { list-style: none; display: flex; flex-wrap: wrap; gap: .35rem; padding: .5rem 0 0; margin: 0; }
.facet-chips a {
  display: inline-flex; gap: .35rem; align-items: center;
  border: 1px solid var(--accent); border-radius: var(--radius);
  padding: .15rem .45rem; font-size: .78rem; text-decoration: none;
  background: var(--accent); color: var(--accent-ink);
}

/* --- product grid --------------------------------------------------------
   FOUR across on desktop. The catalogue is 22,069 SKUs; three tiles per row makes
   browsing it feel longer than it is.

   The column count is not set directly — auto-fill divides the space and the MIN track
   decides how many fit, so the number below is doing the work. The arithmetic, at the
   1.5rem (24px) gap:

     grid width = .wrap 1280 - 2rem padding - 220px facet rail - 2rem gap = 996px max
     4 columns  = 4x190 + 3x24 =  832px   fits from 832px up
     5 columns  = 5x190 + 4x24 = 1046px   never fits, since the grid caps at 996px

   So 190px pins it at four from a ~832px grid all the way to the cap, instead of jumping
   to five on a wide monitor. 200px was the previous value and gave four only at the very
   top of that range - hence three on a typical 1128px window.

   Still auto-fill, so it degrades on its own: three, then two, then one as the viewport
   narrows, with no media query to keep in sync. Raising this number REDUCES the column
   count - if you want five, lower it to ~180. */
.grid {
  list-style: none;
  margin: 1rem 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: 1.5rem;
}

.card { display: flex; flex-direction: column; gap: .35rem; }

/*  THE WHOLE-TILE LINK — the image and the product name, which should read as the card rather
    than as a blue link. `color: inherit` is right for that and stays.

    ‼ IT ALSO CATCHES EVERY OTHER DIRECT-CHILD ANCHOR IN A CARD, which is a wider net than it
    looks, because the anonymous branches of these tiles put their links at exactly this level.
    At (0,1,1) this beats a bare `.btn-primary` or `.price-signin` at (0,1,0), so those two are
    raised to `a.` forms further down rather than being fought with !important here. Anything new
    that is a button or a link INSIDE a card needs the same treatment, or it silently turns black:
    the rule does not fail, it just wins. */
.card > a { text-decoration: none; color: inherit; display: block; }
.card img { border: 1px solid var(--line); border-radius: var(--radius); aspect-ratio: 1; object-fit: contain; background: #fff; }
.no-image { display: block; aspect-ratio: 1; background: #f2f0ec; border: 1px solid var(--line); border-radius: var(--radius); }
/* Hero-shaped placeholder on the admin artwork editor. Only the admin uses it, but it stays
   here with the base class it modifies rather than in admin.css - a variant of a storefront
   class does not belong in a stylesheet the storefront never loads. */
.no-image.wide { aspect-ratio: 16/5; }
.card-name { display: block; margin-top: .5rem; font-size: .9rem; line-height: 1.35; }
.card-meta { display: flex; gap: .5rem; font-size: .75rem; color: var(--muted); }
/* A sale price and the level price it replaced. Both are the CUSTOMER'S OWN level - see the
   note in Product.cshtml for why that distinction is the whole safety argument.

   The struck price is muted and smaller rather than merely struck through: it is reference,
   not an offer, and at equal weight the eye reads two prices and has to work out which one it
   is being asked to pay. */
.price-sale { color: var(--accent); }

/* "Sale" beside a grid price. A word rather than colour alone - colour is not a distinction
   everybody can see, and this is the whole signal on a tile. */
.sale-flag {
  display: inline-block; margin-left: .35rem; padding: 0 .3rem;
  border-radius: var(--radius); background: var(--accent); color: var(--accent-ink);
  font-size: .7rem; font-weight: 600; text-transform: uppercase; letter-spacing: .03em;
  vertical-align: .05rem;
}
.price-was { display: block; font-size: .8rem; color: var(--muted); font-weight: 400; }

.price-gate { font-size: .8rem; color: var(--muted); font-style: italic; }
.price-gate.large { font-size: 1rem; font-style: normal; padding: .75rem 0; }

/* gap drops from 1rem to .5rem now that the row carries page numbers as well as Previous and
   Next — at 1rem a run of digits reads as separate controls rather than one strip. wrap so a
   long pager on a phone breaks onto a second line instead of overflowing. */
.pager { display: flex; gap: .5rem; align-items: center; justify-content: center; padding: 2rem 0; flex-wrap: wrap; }

/* Numbers get a touch target, not just a text link — this row is the one thing on the page
   people aim at repeatedly, often on a phone. */
.pager a, .pager .pager-current {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 2rem; padding: .25rem .5rem; border-radius: var(--radius);
}
.pager .pager-current { background: var(--accent); color: var(--accent-ink); font-weight: 600; }
.pager .pager-gap { color: var(--muted); padding: 0 .15rem; }

/* --- product page -------------------------------------------------------- */
.product { display: grid; grid-template-columns: minmax(280px, 480px) 1fr; gap: 2.5rem; align-items: start; }
@media (max-width: 800px) { .product { grid-template-columns: 1fr; } }

.product-media img { border: 1px solid var(--line); border-radius: var(--radius); }
/* Thumbnails are links now, not decoration — see the note in Pages/Product.cshtml. The
   focus ring is deliberately not suppressed: this is the keyboard route to an item's other
   photographs, and until these were links there was no route at all. */
.thumbs { list-style: none; display: flex; flex-wrap: wrap; gap: .5rem; padding: .5rem 0 0; margin: 0; }
.thumbs a { display: block; border: 1px solid var(--line); border-radius: var(--radius); padding: 2px; line-height: 0; }
.thumbs a:hover { border-color: var(--accent); }
.thumbs li.current a { border-color: #e08b2c; box-shadow: 0 0 0 2px rgba(224,139,44,.25); }

.switcher { margin: 1rem 0; }
.switcher-label { display: block; font-size: .8rem; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); }
.switcher ul { list-style: none; display: flex; gap: .5rem; padding: .5rem 0 0; margin: 0; flex-wrap: wrap; }
.switcher li a {
  display: inline-flex; align-items: center; gap: .4rem;
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: .35rem .7rem; font-size: .85rem; text-decoration: none; color: var(--ink);
}
.switcher li.current a { border-color: var(--accent); background: var(--accent); color: var(--accent-ink); }

.axis { margin: .75rem 0; }
.axis-name { display: block; font-size: .8rem; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); }
.axis-values { list-style: none; display: flex; gap: .5rem; padding: .35rem 0 0; margin: 0; flex-wrap: wrap; }
.axis-values li { border: 1px solid var(--line); border-radius: var(--radius); padding: .3rem .65rem; font-size: .85rem; }

.bullets { padding-left: 1.1rem; }
.description { margin: 1rem 0; }

.prop65 {
  margin: 1.5rem 0;
  padding: .75rem 1rem;
  background: var(--warn-bg);
  border: 1px solid var(--warn-line);
  border-radius: var(--radius);
  font-size: .85rem;
}

/* --- SKU table ----------------------------------------------------------- */
.skus { margin-top: 3rem; overflow-x: auto; }
.skus table { border-collapse: collapse; width: 100%; font-size: .875rem; }
.skus th, .skus td { border-bottom: 1px solid var(--line); padding: .5rem .75rem; text-align: left; }
.skus thead th { background: #faf9f7; font-size: .75rem; text-transform: uppercase; letter-spacing: .04em; }

/* --- home ---------------------------------------------------------------- */
.hero { padding: 2rem 0; border-bottom: 1px solid var(--line); }
.market, .featured { padding: 1.5rem 0; }

/* Section heading bar. A band across the page, title left and its "shop all" right, so a
   market reads as a section rather than as an oversized link.

   A NEUTRAL BAND, NOT THE EVERGREEN. Accent was suggested and would be right for one bar; the
   home page stacks four or five of them, and at that repetition a saturated band stops
   separating sections and starts being the page. The accent stays on the action, which is the
   thing that benefits from standing out. Changing the two lines below is how to try the other
   way - and it is a token, so a theme file can. */
.section-bar {
  display: flex; align-items: baseline; justify-content: space-between; gap: 1rem;
  flex-wrap: wrap;                       /* long market name + action: wrap, never overlap */
  background: var(--band-bg);
  border-bottom: 2px solid var(--line);
  padding: .6rem .9rem;
  border-radius: var(--radius-card) var(--radius-card) 0 0;
}
.section-bar h2 { margin: 0; font-size: 1.15rem; letter-spacing: .01em; }
.section-bar h2 a { text-decoration: none; color: var(--ink); }
.section-bar h2 a:hover { color: var(--accent); }
.section-all {
  font-size: .85rem; font-weight: 600; text-decoration: none; color: var(--accent);
  white-space: nowrap;
}
.section-all:hover { text-decoration: underline; }

.tile-row { list-style: none; display: flex; flex-wrap: wrap; gap: .75rem; padding: 0; margin: .9rem 0 0; }
.tile-row a {
  display: block; border: 1px solid var(--line); border-radius: var(--radius-card);
  padding: .5rem 1rem; text-decoration: none; font-size: .9rem; color: var(--ink);
  transition: border-color .12s ease, box-shadow .12s ease;
}
.tile-row a:hover { border-color: var(--accent); box-shadow: 0 2px 10px rgb(0 0 0 / .08); }

/* A tile WITH artwork becomes a card. Without one it stays the text chip it has always been —
   18 of 55 pages have a tile, and a placeholder box on the other 37 would make a finished
   page look broken. The two sit side by side deliberately: a chip next to a card is also how
   merchandising sees which pages still want a picture. */
.tile-row .has-tile { flex: 0 1 15rem; }
.tile-row .has-tile a { padding: 0; overflow: hidden; }
.tile-row .has-tile img {
  display: block; width: 100%; aspect-ratio: 4 / 3; object-fit: cover;
  background: #f2f0ec;
}

/* Centred label under the picture. min-height holds two lines, so "Wreaths & Garlands" next to
   "Lights" does not make one card taller than its neighbour and break the row's baseline -
   the same class of problem the showroom logos had, and it is worth pre-empting here because
   category names are not going to get shorter. */
.tile-row .has-tile span {
  display: flex; align-items: center; justify-content: center; text-align: center;
  padding: .55rem .75rem; min-height: 2.9rem; line-height: 1.25; font-weight: 600;
}

/* Motion is a nicety here and some people are made ill by it. The border and shadow still
   change on hover - only the easing goes. */
@media (prefers-reduced-motion: reduce) { .tile-row a { transition: none; } }

/* Subcategory tiles on a category page. The row itself is .tile-row, shared with the home
   page; this wrapper exists to separate it from the product grid below, which .tile-row's own
   top-only margin does not do. */
.tile-row-nav { margin-bottom: 1.5rem; }
.tile-row-nav .tile-row { margin-top: .75rem; }

/* NARROWER THAN THE HOME PAGE'S, and the reason is arithmetic rather than taste. At the home
   page's 15rem basis, Trees' five children came to 5x240 + 4x12 = 1248px against about 1200
   of content - so the row wrapped and left Tabletop Trees alone on a line of its own, which
   reads as a mistake rather than a fifth option.

   11rem fits six across at the full width and still shrinks cleanly. These are navigation
   between sections of the catalogue, sitting above the products; the home page's are the
   main event on a page with room to spare. Different jobs, different sizes - the shared
   card-or-chip treatment is what actually matters for consistency.

   Left as flex-basis rather than a fixed width so a page with three children spreads them
   instead of leaving a gap. */
.tile-row-nav .tile-row .has-tile { flex: 0 1 11rem; }

/* The label can be one line here: these names are short and the row is a navigation strip,
   so the 2.9rem two-line reservation the home cards need only adds dead space. */
.tile-row-nav .tile-row .has-tile span { min-height: 0; padding: .45rem .6rem; font-size: .85rem; }

/* The home page's own editable block. Spacing only - everything inside it is the author's
   markup and their chosen classes, so imposing a width or a type scale here would fight
   whatever they wrote. */
.home-body { padding: 1.5rem 0; }
.home-body > :first-child { margin-top: 0; }
.home-body > :last-child { margin-bottom: 0; }

/* --- pricing ------------------------------------------------------------- */
/* The price row on a tile. flex + baseline so a struck "was", the current price and a discount
   badge sit on one line and wrap together — the same arrangement as .openbox-price, which is
   the card this was asked to match. */
.card-price { font-weight: 600; font-size: .95rem; display: flex; flex-wrap: wrap; align-items: baseline; gap: .35rem; }

/* Inside a tile the struck price sits INLINE before the current one. .price-was is display:block
   elsewhere (the product table and the cart, where it belongs under the price), so it is
   overridden here rather than changed there. */
.card-price .price-was { display: inline; text-decoration: line-through; }

.card-saving { display: block; font-size: .8rem; color: #8a1c1c; margin-top: .15rem; }

/* --- Add to project menu -------------------------------------------------
   One control replacing a select plus a button, matching the site this replaces. A <details>
   so it works with no JavaScript: closed it is a button, open it is a list of buttons.

   position: relative on the details and absolute on the panel so opening it OVERLAYS rather
   than pushing the buy table down - a menu that reflows the page under the pointer is how you
   click the wrong row. */
.project-menu { position: relative; display: inline-block; }
.project-menu > summary { list-style: none; cursor: pointer; display: inline-block; }
.project-menu > summary::-webkit-details-marker { display: none; }
/* The caret is part of the label, so the summary reads as a menu rather than a plain button. */
.project-menu > summary::after { content: " \25BE"; }

.project-menu-panel {
  position: absolute; z-index: 20; right: 0; min-width: 15rem; margin-top: .25rem;
  background: #fff; border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: 0 4px 16px rgb(0 0 0 / .12); padding: .35rem 0; text-align: left;
}
.project-menu-panel ul { list-style: none; margin: 0; padding: 0; }
/* Full-width targets: in a menu the whole row should be clickable, not just the words. */
.project-menu-panel button,
.project-menu-panel > a {
  display: block; width: 100%; text-align: left; padding: .4rem .75rem;
  background: none; border: 0; font: inherit; color: var(--ink); cursor: pointer;
}
.project-menu-panel button:hover,
.project-menu-panel > a:hover { background: #f2f0ec; }

/* Create and View are separated from the projects by a rule - they are different in kind, and
   the site this replaces set them apart the same way. */
.project-menu-new { border-top: 1px solid var(--line); margin-top: .35rem; padding: .5rem .75rem .25rem; }
.project-menu-new input { width: 100%; padding: .3rem .4rem; border: 1px solid var(--line); border-radius: var(--radius); }
.project-menu-new button { padding-left: 0; padding-right: 0; font-style: italic; }
.project-menu-panel > a { font-style: italic; border-top: 1px solid var(--line); }
.price-from { font-weight: 400; font-size: .78rem; color: var(--muted); }
.price-none { color: var(--muted); }
/* Where a price would be, for a visitor who is not entitled to one. Deliberately quieter than
   a price and louder than .price-none - it is an invitation, not a value, and it must not be
   mistaken for the number it replaces. Same weight as body text so a grid of them does not
   read as a grid of prices. */
/*  `a.` RATHER THAN A BARE CLASS, and that is the whole point of it. This lives inside a
    `.card` on every grid tile, where `.card > a` sets `color: inherit` at (0,1,1) — so at
    (0,1,0) this rule lost, and "Sign in for pricing" rendered as plain near-black body text
    with nothing to say it was a link. It is the only thing an anonymous visitor can click to
    find out what something costs. */
a.price-signin { display: inline-block; font-size: .85rem; font-weight: 500; color: var(--accent); }
/* The same message on the product page, where it explains a missing table column rather than
   standing in for one figure. */
.price-signin-note { font-size: .9rem; color: var(--muted); margin: 0 0 .5rem; }
.price { font-weight: 600; }

/* --- cart ---------------------------------------------------------------- */
.cart-link { position: relative; text-decoration: none; font-weight: 600; color: var(--ink); white-space: nowrap; }
/* Account menu in the header. Same <details> mechanism as the main nav — click, not hover
   — so the two menus behave identically and neither needs JavaScript. */
.account-nav { display: flex; align-items: center; gap: 1rem; }

/* .help-link moved to admin.css - it lives in the admin bar now, not this header. */
.account-drop { position: relative; }
.account-drop > summary { font-size: .9rem; white-space: nowrap; }

/* Right-aligned: the account menu sits at the end of the header, so a left-anchored panel
   would open off the edge of the viewport. */
.nav-sub-right { left: auto; right: 0; }
.cart-badge {
  display: inline-block; min-width: 1.25rem; padding: 0 .3rem; margin-left: .25rem;
  background: var(--accent); color: var(--accent-ink);
  border-radius: 999px; font-size: .72rem; line-height: 1.25rem; text-align: center;
}
/* The badge is rendered at zero and hidden, so the scripts have an element to update after an
   add. `display` above outranks the browser's own [hidden] rule, so it has to be said here. */
.cart-badge[hidden] { display: none; }

.added-banner {
  background: #e8f3ee; border: 1px solid var(--accent);
  border-radius: var(--radius); padding: .6rem .9rem; margin: 0 0 1rem;
}

.buy-table { margin: 1.25rem 0; }
.buy-table table { width: 100%; border-collapse: collapse; font-size: .9rem; }
.buy-table th, .buy-table td { border-bottom: 1px solid var(--line); padding: .5rem .6rem; text-align: left; vertical-align: top; }
.buy-table thead th { background: #faf9f7; font-size: .72rem; text-transform: uppercase; letter-spacing: .04em; }
.buy-table .num { text-align: right; }
.buy-table input[type=number] { width: 4.5rem; padding: .3rem; border: 1px solid var(--line); border-radius: var(--radius); text-align: right; }
.row-desc { display: block; }
.row-item { display: block; font-size: .75rem; color: var(--muted); }

/*  The per-SKU "Copy link" button in the buy table. Quiet on purpose: it is a tool for a rep
    building a spreadsheet, not a call to action for a shopper, and it sits in a table that
    already asks for a quantity and an Add to Cart on every line. Sized and coloured like the
    item number above it so the cell reads as one block rather than gaining a control. */
.row-copy { display: block; font-size: .75rem; padding: 0; margin-top: .15rem; }

/* The item number under the product title. Trade buyers navigate by it, so it reads at
   body size rather than as fine print, and the code face makes it obvious it is meant to
   be copied rather than read as prose. */
.product-sku { margin: -.25rem 0 1rem; color: var(--muted); font-size: .85rem; }
.product-sku code { font-size: 1rem; color: var(--ink); letter-spacing: .02em; }
.row-flag { display: inline-block; font-size: .7rem; color: var(--muted); border: 1px solid var(--line); padding: 0 .3rem; border-radius: var(--radius); }
.buy-actions { padding-top: .75rem; display: flex; gap: .75rem; justify-content: flex-end; }

.btn-primary {
  background: var(--accent); color: var(--accent-ink); border: 1px solid var(--accent);
  padding: .55rem 1.4rem; border-radius: var(--radius); font-size: .9rem; cursor: pointer;
}
.btn-secondary {
  background: #fff; color: var(--accent); border: 1px solid var(--accent);
  padding: .55rem 1.2rem; border-radius: var(--radius); font-size: .9rem; cursor: pointer;
}
/* An anchor styled as a button. Both rules are needed: an <a> is inline, so the padding above
   would not reserve vertical space outside a flex row, and it carries the underline a button
   never has. .btn-link is deliberately NOT included - that one is meant to look like a link.

   ‼ THE COLOURS ARE REPEATED HERE ON PURPOSE, AND ARE NOT REDUNDANT WITH THE RULES ABOVE.
   `.card > a { color: inherit }` is (0,1,1) and `.btn-primary` is (0,1,0), so inside a grid
   tile the button's own colour LOST and the label rendered in body ink. On .btn-primary that
   meant #1c1c1c on the #1f4d3d fill - 1.8:1, against the 4.5:1 minimum - so "Sign in to order"
   on an open box tile was a solid green button with text nobody could read. Reported from the
   open box page, 2026-08-14.

   Restating them at (0,1,1), after that rule, is what fixes it. !important would too, and would
   then have to be beaten by everything that legitimately recolours a button - .is-dirty is two
   rules further down and would have needed its own. */
a.btn-primary, a.btn-secondary { display: inline-block; text-decoration: none; }
a.btn-primary   { color: var(--accent-ink); }
a.btn-secondary { color: var(--accent); }

.btn-link { background: none; border: none; color: var(--accent); cursor: pointer; text-decoration: underline; font-size: .85rem; padding: 0; }

/* size tiles - the current site's size selector */
.size-tiles { margin: 1rem 0; }
.size-tiles ul { list-style: none; display: flex; flex-wrap: wrap; gap: .5rem; padding: .4rem 0 0; margin: 0; }
.size-tiles li a {
  display: flex; flex-direction: column; align-items: center; gap: .2rem;
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: .4rem .5rem; min-width: 68px; text-decoration: none; color: var(--ink); font-size: .78rem;
}
.size-tiles li.current a { border-color: #e08b2c; box-shadow: 0 0 0 2px rgba(224,139,44,.25); }

/* Axis 2 — the light option, the pack size. Text rather than image tiles, because the values
   it carries ("Clear", "Multi", "Warm White") differ in a way a 56px swatch cannot show.
   Styled like .switcher so the two rows of choices on a product page read as one system. */
.opt-picker { margin: 1rem 0; }
.opt-picker ul { list-style: none; display: flex; flex-wrap: wrap; gap: .5rem; padding: .4rem 0 0; margin: 0; }
.opt-picker li a {
  display: inline-block; border: 1px solid var(--line); border-radius: var(--radius);
  padding: .35rem .7rem; font-size: .85rem; text-decoration: none; color: var(--ink);
}
.opt-picker li a:hover { border-color: var(--accent); }
.opt-picker li.current a { border-color: var(--accent); background: var(--accent); color: var(--accent-ink); }

/* The buy-table row matching the picker. A left edge rather than a filled background: the row
   holds a price and a stock figure, and tinting those reads as a status they do not have. */
.buy-table tr.row-shown > * { background: rgba(224,139,44,.07); }
.buy-table tr.row-shown th[scope=row] { box-shadow: inset 3px 0 0 #e08b2c; }

/* The selected SKU's own description, under the item number. */
.product-variant-desc { margin: .15rem 0 .75rem; color: var(--muted); font-size: .95rem; }

/* ---------------------------------------------------------------------------
   THE SHIPPING ESTIMATE, under the buy table.

   Set off by a rule and a tinted panel rather than left inline, because it is the one thing in
   the product-info column that ASKS the visitor for something. Sitting flush against the buy
   actions it read as another line of the table.

   The figures are deliberately NOT styled like .price. They are a carrier's charge for a box,
   not this customer's price for the goods, and giving them the price face is how "$26.32" ends
   up read as the item costing that.
   --------------------------------------------------------------------------- */
.ship-estimate { margin: 1.5rem 0 0; border-top: 1px solid var(--line); padding-top: 1rem; }
.ship-estimate h2 { font-size: 1rem; margin: 0 0 .5rem; }

.ship-estimate-form { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; }
.ship-estimate-form label { font-size: .85rem; color: var(--muted); }
.ship-estimate-form input[type=text] {
  padding: .35rem .5rem; border: 1px solid var(--line); border-radius: var(--radius);
  font-size: .9rem; width: 7rem;
}

/* Empty until there is an answer, so no space is reserved for one. */
.ship-estimate-result:not(:empty) { margin-top: .75rem; display: flex; flex-wrap: wrap; gap: 1rem; }
.ship-rate { font-size: 1rem; font-weight: 600; }
.ship-problem { font-size: .85rem; color: var(--muted); }

/* The disclaimer. Long on purpose — every clause of it is a way the number can be misread. */
.ship-estimate .note { margin-top: .6rem; font-size: .78rem; line-height: 1.45; }

/* The button while the server is calling FedEx. Same treatment as .es-btn in the admin area:
   the state is added by script, so a page without script never shows a control that looks
   stuck. */
.ship-estimate-form button.is-busy { opacity: .6; cursor: progress; }

/* ---------------------------------------------------------------------------
   THE ITEM LIST — the cart's and a project's, one style for both.

   .line-table rides on .admin-table (which is not admin-only here — see the note above the
   admin block below) and adds only what a line of MERCHANDISE needs: a thumbnail column and a
   quantity box. The cart used to carry its own `.cart table` rules at a different font size and
   padding from the project's `.admin-table`, so the same forty items were laid out two ways
   depending on which page you reached them from. There is one row partial now, and one rule set
   to go with it.
   --------------------------------------------------------------------------- */
.line-table .cart-img { width: 64px; }
.line-table input[type=number] { width: 4.5rem; padding: .3rem; border: 1px solid var(--line); border-radius: var(--radius); text-align: right; }
/* The staged/changed/removed left edges (.row-added and friends) are defined once, further up. */

/* Also used by Quick Order, which lists items without the rest of the cart. */
.cart-img img { border: 1px solid var(--line); border-radius: var(--radius); }
.cart-actions { display: flex; gap: 1rem; align-items: center; padding-top: 1rem; }
.cart-note { margin-top: 1.5rem; }

/* --- The Orchard cart offer ----------------------------------------------
   Same shape as .cart-actions: the accept button and "No thanks" on one row, the decline
   deliberately a .btn-link rather than a second button so the choice does not look balanced -
   taking the cart is the expected answer. wrap because the accept button's label carries a
   count and grows. */
.legacy-cart-actions { display: flex; gap: 1rem; align-items: center; flex-wrap: wrap; margin-top: 1rem; }

/* --- Empty the cart ------------------------------------------------------
   The only irreversible control on the page, so it is set apart from the buttons above it
   rather than lined up with them. Closed by default; <details> handles that with no script.

   #8a1c1c is the red already used by .discount-badge and .card-saving - a second red for
   "danger" would be a new colour in the palette to say something the existing one already
   says. */
.cart-clear { margin-top: 2rem; border-top: 1px solid var(--line); padding-top: 1rem; }
.cart-clear-toggle { cursor: pointer; color: #8a1c1c; font-size: .9rem; }
/* list-style off on both, because engines differ on which one draws the marker. */
.cart-clear > summary { list-style: none; }
.cart-clear > summary::-webkit-details-marker { display: none; }
.cart-clear-panel { margin-top: .75rem; max-width: 28rem; }
.cart-clear-panel p { margin: 0 0 .5rem; font-size: .9rem; color: var(--muted); }
.cart-clear-panel label { display: block; font-size: .9rem; margin-bottom: .25rem; }
.cart-clear-panel input { width: 8rem; padding: .35rem .4rem; border: 1px solid var(--line);
  border-radius: var(--radius); font: inherit; margin-right: .5rem; }
.btn-danger { border: 1px solid #8a1c1c; background: #8a1c1c; color: #fff;
  padding: .45rem .9rem; border-radius: var(--radius); font: inherit; cursor: pointer; }
.btn-danger:hover { background: #6d1616; border-color: #6d1616; }

/* ===========================================================================
   The client document — a designer's presentation for their own customer.

   Two audiences, one stylesheet: on screen it sits inside the normal site chrome, and on paper
   the chrome is gone and only the document remains. The print rules are at the foot of this
   block rather than in a separate file so the two cannot be edited apart.
   =========================================================================== */
.doc { max-width: 60rem; }
.doc-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 2rem;
            border-bottom: 2px solid var(--ink); padding-bottom: .75rem; margin-bottom: 1.25rem; }
.doc-head h1 { margin: 0; }
/* Logo beside the title, not above it, so the eye reads brand-then-job in one line. */
.doc-brand { display: flex; align-items: flex-start; gap: 1rem; }
.doc-logo { max-height: 64px; max-width: 200px; width: auto; height: auto; flex: none; }
.doc-by { margin: .25rem 0 0; font-size: 1rem; color: var(--muted); }
.doc-meta { text-align: right; font-size: .85rem; color: var(--muted); }
.doc-meta p { margin: 0 0 .2rem; }
.doc-intro { max-width: 46rem; }

.doc-images { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
              gap: 1rem; margin: 1.5rem 0; }
.doc-images img { width: 100%; height: auto; border: 1px solid var(--line); border-radius: var(--radius); }

/* The availability statement — the reason this document is worth sending, so it is given the
   weight of a callout rather than a line of body copy. */
.doc-stock { background: #f4f8f6; border-left: 4px solid var(--accent);
             padding: .9rem 1.1rem; margin: 1.5rem 0; border-radius: var(--radius); }
.doc-stock p { margin: 0 0 .3rem; }
.doc-stock-lead { font-size: 1.05rem; }
.doc-note { font-size: .8rem; color: var(--muted); }

.doc-table { width: 100%; border-collapse: collapse; font-size: .9rem; }
.doc-table th, .doc-table td { border-bottom: 1px solid var(--line); padding: .55rem .5rem;
                               text-align: left; vertical-align: middle; }
.doc-table .num { text-align: right; }
.doc-thumb { width: 80px; }
.doc-thumb img { border: 1px solid var(--line); border-radius: var(--radius); display: block; }
.doc-sku { font-size: .8rem; color: var(--muted); }
.doc-foot { margin-top: 2rem; font-size: .8rem; color: var(--muted); }

.doc-tools { margin-bottom: 1.5rem; }
.doc-links { margin-top: 1rem; }
/* Wide enough that the whole URL is visible before copying — a truncated link that looks
   complete is the one somebody pastes. */
.doc-link { width: 100%; min-width: 22rem; font-family: ui-monospace, Consolas, monospace;
            font-size: .8rem; padding: .35rem .5rem; border: 1px solid var(--line);
            border-radius: var(--radius); background: #fff; }

@media print {
    /* Everything that is not the document. The site chrome, the sharing controls, the print
       button itself — a printed page showing a "Print" button is a tell that nobody thought
       about paper. */
    .site-header, .main-nav, .site-footer, .breadcrumb,
    .consent-banner, .env-banner, .viewas-banner { display: none !important; }

    /* Its own rule, not folded into the list above, and that is deliberate: the convention
       check parses the selector before each "{" and cannot see the FIRST rule inside a media
       block. Every class in that first list is defined elsewhere too, so only .no-print would
       be reported missing — which is a false alarm about a real rule, the most expensive kind. */
    .no-print { display: none !important; }

    /* Colour-managed printers drop light backgrounds by default, which would take the callout
       with them. Applied to the whole document rather than just the callout: this is a
       designer's presentation to their client, and the photographs of the job are the point of
       it. Browsers that refuse still show the border and the images. */
    .doc, .doc-stock { -webkit-print-color-adjust: exact; print-color-adjust: exact; }

    body { font-size: 11pt; }
    .doc { max-width: none; }
    main { padding: 0 !important; margin: 0 !important; }

    /* A product must not be split across a page break, and a header row alone at the foot of
       a page is worse than a shorter page. */
    .doc-table tr, .doc-images img { break-inside: avoid; page-break-inside: avoid; }
    .doc-table thead { display: table-header-group; }

    a[href]::after { content: ""; }   /* no printed URLs after every link */

    @page { margin: 15mm; }
}

/* Star a design for later. The note sits beside the button rather than under it, because what
   it says — that saving is private — is the thing somebody wants to know BEFORE clicking. */
.design-bookmark { display: flex; align-items: center; gap: .75rem; flex-wrap: wrap; margin: 1rem 0; }
.design-bookmark .note { max-width: 22rem; }

/* The bookmark ribbon. Sized in em so it tracks whatever text it sits beside, and the small
   negative baseline shift is what stops an inline SVG riding high next to its label. */
.bm-icon { width: 1em; height: 1em; vertical-align: -.125em; margin-right: .3em; }

/* The same star on a gallery card. margin-top:auto pins it to the bottom of the card so a row
   of cards with titles of different lengths still lines its stars up. */
.card-bookmark { margin-top: auto; padding-top: .35rem; }

/* Photographs a customer uploads against their own project. Storefront, not admin — this page
   never loads admin.css, so these live here with the rest of the account vocabulary. */
.project-photos { margin-top: 2.5rem; border-top: 1px solid var(--line); padding-top: 1.5rem; }

/* --- Collapsible project sections ----------------------------------------
   Photos, and Publish where a browser cannot show it as a dialog. (Design Details was the third
   and is gone: the details TABLE at the top of the page is the editor now.)

   DECLARED AFTER .project-photos ON PURPOSE. That class sets margin-top, padding-top and
   border-top for the old stacked-block layout and is still on the element. Equal specificity, so
   the later rule wins - and the shorthands below (border, padding) are what actually undo the
   single-side properties above. Moving this block earlier in the file silently restores a stray
   rule above the panel. (.project-edit-block was the other one, and no longer exists at all -
   there is a note where it was.)

   The marker is drawn here rather than left to the browser: the native one differs in shape
   and position across engines, and these sit in a column where they should line up. */
.project-section {
  margin-top: 1rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0 1rem 1rem;
}
/* No dead space under a closed panel - it is a header and nothing else. */
.project-section:not([open]) { padding-bottom: 0; }

.project-section > summary {
  /* Negative side margins so the hover band spans the full panel while the CONTENT stays
     inset by the panel's own padding. */
  margin: 0 -1rem;
  padding: .7rem 1rem;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: .5rem;
  /* Both, because engines disagree about which one draws the default triangle. */
  list-style: none;
}
.project-section > summary::-webkit-details-marker { display: none; }
.project-section > summary:hover { background: #faf9f7; }

.project-section > summary::before {
  content: "\25B6";
  font-size: .65em;
  color: var(--muted);
  transition: transform .15s ease;
}
.project-section[open] > summary::before { transform: rotate(90deg); }

/* The heading keeps its level for the document outline; only its display shrinks to panel size. */
.project-section > summary h2 { margin: 0; font-size: 1.05rem; }

/* "Filled in", beside the heading it belongs to. Green plus a shape, never colour alone - the
   tick is the signal and the colour only reinforces it, so it still reads for the ~8% of men
   with a red-green deficiency. It carries an aria-label because a bare glyph announces as
   "check mark" with no idea what has been checked.

   UNSCOPED, because it is no longer only in a summary. It used to be `.project-section > summary
   .section-done`; the permission tick now sits beside the Publish BUTTON in the actions row,
   since project-publish.js moves that panel into a dialog and the summary it lived in goes with
   it. Scoping it to the panel meant the tick silently vanished the moment the dialog appeared. */
.section-done {
  color: #166534; font-weight: 700; margin-left: .35rem;
}

@media (prefers-reduced-motion: reduce) {
  .project-section > summary::before { transition: none; }
}

/* --- The project's details table, which IS the editor ---------------------
   Same table whether it is a document or a form: published renders text in the cells,
   unpublished renders fields in them. The point of doing it this way is that the two look like
   the same thing, because they are - so the only rules here size the controls to the cell.

   The label column keeps its width whichever mode is showing, or the page would jump between a
   published project and an editable one. */
.project-detail th[scope=row] { width: 12rem; font-weight: 600; }

/* "Edit details", under the document. .btn-link already makes it an underlined text link rather
   than a button, which is the low-profile end of the vocabulary and right for a control that is
   on screen the whole time you are only reading.

   inline-flex so the pencil and the word sit on one baseline; the SVG is inline in the markup
   because this site loads no third-party resources and an icon font would be one. */
.project-detail-edit { margin: .4rem 0 0; }
.project-detail-edit .btn-link { display: inline-flex; align-items: center; gap: .35rem; }
/* THE ROW HEADER IS THE LABEL - <th><label for=...>. One element doing both jobs, so the column
   of names reads the same in either mode and clicking one focuses its field. */
.project-detail th[scope=row] label { cursor: pointer; }
/* Top, not the table's inherited middle: a four-row textarea centred against its name leaves the
   name floating in the middle of an empty column. */
.project-detail th, .project-detail td { vertical-align: top; }

.project-detail td input[type=text],
.project-detail td input[type=date],
.project-detail td textarea {
  width: 100%;
  /* Not wider than a comfortable measure. A description box spanning a 1280px page is a
     paragraph nobody can read back. */
  max-width: 44rem;
  padding: .4rem .55rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  font: inherit;
  /* Inherited from the table at .875rem, which is fine to read and small to TYPE into. */
  font-size: 1rem;
}
.project-detail td textarea { resize: vertical; }
.project-detail td small { display: block; margin-top: .25rem; color: var(--muted); }

/* --- Editing the item list, beside the item list -------------------------
   The add-item row and the upper Save sit directly above the table they act on, and the lower
   Save directly below it. Both blocks belong to #project-edit-form through the HTML5 form
   attribute rather than by nesting - see Project.cshtml.

   .cart-add carries the field styling on the add block - .cart-add .field and .cart-add-row are
   scoped under it, along with the rule and the top margin that separate the block from the
   add-to-cart row above. These two add only what sitting either side of a TABLE needs, which the
   cart's version of this block never does. */
/* Off the table below it, which .cart-add has no bottom margin for. */
.project-line-edit { margin-bottom: 1rem; }
/* Clear of the total and the notes that follow the table. */
.project-line-save { margin-bottom: 1.5rem; }

/* THE SAVE BUTTON WHILE SOMETHING IS STAGED.

   Project edits live only in the page until Save is pressed, so an unnoticed one is a lost one.
   All three Save buttons carry this together - project-edit.js toggles it on every one, because a
   designer who staged a quantity at the table and scrolled up must not find a calm button waiting
   there.

   #b45309 is the amber .warn already uses on this page, so "unsaved" reads the same whether it
   arrives as a sentence or as a button. THE RING IS THE SIGNAL AND THE COLOUR REINFORCES IT: a
   green-to-amber swap alone is exactly the pair that fails a red-green deficiency, and the words
   in [data-project-changes] carry the message for anyone using neither. */
.btn-primary.is-dirty {
  background: #b45309;
  border-color: #b45309;
  box-shadow: 0 0 0 3px rgba(180, 83, 9, .25);
}

/* The CART's Save changes is a .btn-secondary, and stays one: the primary action on that page is
   Continue to shipping, and promoting Save would put two solid green buttons side by side and
   leave the buyer choosing between them. It still has to shout when something is staged, so the
   dirty state fills it the same amber - white on #b45309 clears 4.5:1, which the outline
   treatment on white does not. */
.btn-secondary.is-dirty {
  background: #b45309;
  border-color: #b45309;
  color: #fff;
  box-shadow: 0 0 0 3px rgba(180, 83, 9, .25);
}

/* --- The publish modal ----------------------------------------------------
   Built by project-publish.js, which MOVES the Publish panel's content into a <dialog> rather
   than rendering a second copy of it. So everything inside is styled by the rules it already had;
   what follows is only the box around it.

   The native element, not a div: focus trapping, Escape, inertness of the page behind and the
   top-layer stacking all come free and all of them are easy to get subtly wrong by hand. */
.project-dialog {
  width: min(38rem, calc(100vw - 2rem));
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  color: var(--ink);
  background: var(--bg);
}
.project-dialog::backdrop { background: rgba(0, 0, 0, .45); }

.project-dialog-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: .9rem 1.2rem;
  border-bottom: 1px solid var(--line);
  background: var(--band-bg);
}
.project-dialog-head h2 { margin: 0; font-size: 1.05rem; }

.project-dialog-close {
  background: none; border: none; cursor: pointer;
  font-size: 1.5rem; line-height: 1; color: var(--muted); padding: 0 .25rem;
}
.project-dialog-close:hover { color: var(--ink); }

/* Scrolls inside itself rather than growing past the viewport - the consent wording plus the
   credit field plus a withdraw form is taller than a laptop screen in landscape. */
.project-dialog-body { padding: 1.2rem; max-height: 70vh; overflow-y: auto; }
/* The first form in the panel carried .cart-add, whose top border and margin separated it from
   the heading above it. In the dialog the head does that, so the rule would draw twice. */
.project-dialog-body > .cart-add:first-child { border-top: 0; margin-top: 0; padding-top: 0; }

/* auto-fill, not auto-fit: with one photo, auto-fit stretches the single column to the full
   width and shows a 200px thumbnail blown up across the page. auto-fill keeps the track at its
   own size and leaves the rest of the row empty, which is what a gallery of one should look
   like. */
.photo-grid {
    list-style: none;
    margin: 1rem 0;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
    gap: 1rem;
}
.photo-grid li { display: flex; flex-direction: column; gap: .3rem; align-items: flex-start; }
.photo-grid img {
    width: 100%;
    height: auto;
    border: 1px solid var(--line);
    border-radius: var(--radius);
}

.spec { margin-top: 2.5rem; }
.spec dl { display: grid; grid-template-columns: auto 1fr auto 1fr; gap: .35rem 1rem; font-size: .875rem; max-width: 720px; }
.spec dt { font-weight: 600; }
.spec dd { margin: 0; color: var(--muted); }
@media (max-width: 600px) { .spec dl { grid-template-columns: auto 1fr; } }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}

/* --- group switcher: disclosure panel for large sets (58 ornament colours) --- */
.switcher-panel summary { list-style: none; cursor: pointer; display: flex; align-items: center; gap: .6rem; }
.switcher-panel summary::-webkit-details-marker { display: none; }
.switcher-current { font-size: .85rem; color: var(--muted); }
.swatch-grid {
  list-style: none; margin: .75rem 0 0; padding: .75rem;
  border: 1px solid var(--line); border-radius: var(--radius);
  display: grid; grid-template-columns: repeat(auto-fill, minmax(92px, 1fr)); gap: .5rem;
  max-height: 420px; overflow-y: auto;
}
.swatch-grid li a {
  display: flex; flex-direction: column; align-items: center; gap: .25rem;
  padding: .4rem; border: 1px solid var(--line); border-radius: var(--radius);
  text-decoration: none; color: var(--ink); font-size: .72rem; text-align: center;
}
.swatch-grid li.current a { border-color: #e08b2c; box-shadow: 0 0 0 2px rgba(224,139,44,.25); }

/* --- footer -------------------------------------------------------------- */
.site-footer { border-top: 1px solid var(--line); padding: 1.5rem 0; color: var(--muted); font-size: .85rem; }

/* --- data-dense screens, shared by admin AND the account pages --------------
   The rest of the admin styling lives in wwwroot/css/admin.css. These stay here because they
   are NOT admin-only, and moving them would unstyle customer-facing pages, which never load
   admin.css.

   WHY ANYTHING IS SHARED AT ALL — a real difference from ASA, not laziness. This site's
   account pages are data-dense in a way ASA's retail ones are not: order history, order
   detail, Quick Order, project line items and checkout are all tabular. They took the admin
   table style because it was the right style, not by accident. ASA's storefront has no such
   page, which is the only reason ASA can keep .admin-table admin-only.

   The alternative — rename six customer pages onto a duplicate .data-table — buys a tidier
   split and costs a second table style that drifts from the first.

   deploy/Test-AdminConventions.ps1 enforces the boundary in the other direction: anything in
   admin.css that a non-admin page uses is a build failure.

     .admin-table   Account/Order, Account/Orders, Account/QuickOrder, Checkout, Project,
                    AdminAccess, Tracking
     .admin-panel   AdminAccess, Tracking
     .search-row    Account/QuickOrder
     .status-pill   AdminAccess, Account/Projects, Tracking   (defined up with .badge)
     .warn / .note  Cart, Checkout, Account/Orders, Account/Projects, Account/QuickOrder,
                    Tracking

   Tracking is the first ANONYMOUS page on this list, and it belongs here for the same reason
   the account pages do: a carrier's scan history is a table of dates and places. It is not an
   account page that forgot to check who is reading — see Pages/Tracking.cshtml.cs.

   .added-banner and .error-banner also stay here, and are NOT renamed to ASA's .ok / .err.
   Admin now uses .ok / .err; the storefront keeps its boxes. ASA renders a success message as
   plain green text, and converting Cart's "Added to cart" confirmation to plain text is a
   storefront design change, which this work has no mandate for. Admin matches ASA, the
   storefront keeps its own — exactly the split CLAUDE.md describes.
   --------------------------------------------------------------------------- */
/* ASA's .search-row, and it replaces BOTH names this project had for the same bar -
   .admin-search and .admin-filter, which differed only in that one of them had no rule. */
.search-row { display: flex; gap: .5rem; align-items: center; margin: 1rem 0; flex-wrap: wrap; }
.search-row input, .search-row select {
  padding: .45rem .6rem; border: 1px solid var(--line); border-radius: var(--radius); font: inherit;
}
.search-row input[type=search], .search-row input[type=text] { min-width: 260px; }
.admin-table { width: 100%; border-collapse: collapse; font-size: .875rem; }
.admin-table th, .admin-table td { border-bottom: 1px solid var(--line); padding: .5rem .6rem; text-align: left; vertical-align: middle; }
.admin-table thead th { background: #faf9f7; font-size: .72rem; text-transform: uppercase; letter-spacing: .04em; }
.admin-table .num { text-align: right; }

/*  A cell that must not break mid-phrase. Beside .num because it answers the same kind of
    question: how this column behaves when the table is under width pressure.

    WHAT IT FIXES. An .admin-table is width:100% and lets the browser share the space out, so a
    long first column - a URL in <code>, which has no spaces to break at - takes what it wants and
    starves the short columns to the right. On /admin/redirects that turned "14 Aug" into two
    lines and "Redirect this" into three, and the actions column looked like a bulleted list.
    Reported 2026-08-14.

    Applied to the CELL rather than set on .admin-table wholesale: a referrer or a page title
    genuinely should wrap, and nowrap everywhere would push the table into a horizontal scroll to
    protect a date. */
.admin-table .nowrap { white-space: nowrap; }
.error-banner { background: #fdecea; border: 1px solid #b3261e; border-radius: var(--radius); padding: .6rem .9rem; margin: 0 0 1rem; }

/* --- form vocabulary, shared by admin and the account pages ----------------
   These began as admin-only and moved here when the designer profile editor arrived: a
   customer filling in their own profile needs the same fieldsets, two-column rows and
   checkbox rows that /admin/settings does, and a customer page never loads admin.css.

   The name .admin-form now under-describes it, the same way .admin-table does. Kept anyway
   for the same reason: renaming would touch every admin page to buy a tidier word, and the
   note is cheaper than the churn. deploy/Test-AdminConventions.ps1 is what caught the leak —
   this whole block was in admin.css and the profile page would have rendered unstyled. */
.admin-form { max-width: 760px; }   /* ASA's width */
.admin-form fieldset { border: 1px solid var(--line); border-radius: var(--radius); padding: 1rem 1.2rem; margin: 0 0 1.25rem; }
.admin-form legend { font-size: .78rem; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); padding: 0 .4rem; }
.admin-form label { display: block; margin-bottom: 1rem; }
.admin-form label > span, .field-label { display: block; font-weight: 600; font-size: .85rem; margin-bottom: .25rem; }
.admin-form input[type=text], .admin-form input[type=email], .admin-form input:not([type]), .admin-form textarea {
  width: 100%; padding: .45rem .6rem; border: 1px solid var(--line);
  border-radius: var(--radius); font: inherit;
}
.admin-form small { display: block; color: var(--muted); font-size: .78rem; margin-top: .2rem; }

/* Two fields read together — Title and Slug, City and State. Collapses rather than squeezing. */
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; }
@media (max-width: 700px) { .field-row { grid-template-columns: 1fr; } }

.chk-grid { display: flex; gap: 1.5rem; align-items: flex-end; flex-wrap: wrap; }
.chk { display: flex !important; align-items: center; gap: .4rem; margin-bottom: 0 !important; }
.chk input { width: auto; }
.chk > span { margin-bottom: 0 !important; font-weight: 400 !important; }

/* The per-slide edit form on /admin/settings, in a full-width row under its slide.

   Recessed rather than bordered like a panel: closed, this is just a summary line and should
   read as part of the row above it, not as a second row of data. The left rule is what ties
   the open form back to the slide it belongs to once it is expanded to full height. */
.slide-edit-row > td { background: #faf9f7; border-left: 3px solid var(--line); padding: .4rem .75rem; }
.slide-edit-row summary { cursor: pointer; font-size: .85rem; color: var(--muted); }
.slide-edit-row summary:hover { color: var(--accent); }
.slide-edit-row details[open] summary { margin-bottom: .75rem; font-weight: 600; color: var(--ink); }
.slide-edit-row .admin-form { max-width: 52rem; }

/* Separates "Add a slide" from the last slide's Edit row above it. They ran together, so
   there was no visual boundary between editing an existing slide and creating a new one. */
.slide-add { margin-top: 2rem; }

/* Copy beside an image — artwork upload, a designer's logo. */
.art-row { display: grid; grid-template-columns: 2fr 1fr; gap: 1.5rem; }
@media (max-width: 700px) { .art-row { grid-template-columns: 1fr; } }
.preview { border: 1px solid var(--line); border-radius: var(--radius); max-width: 100%; display: block; margin-bottom: .5rem; }
/* The hero preview is cropped the same way the page crops it. Showing an admin the raw shape
   while the customer sees a 3:1 crop is how somebody uploads a tall photograph, approves it
   on the preview, and finds the top and bottom cut off on the live page. */
.preview.wide { aspect-ratio: 3 / 1; object-fit: cover; object-position: center; width: 100%; }

/* Small affirmative, for a tick beside a satisfied requirement. */
.ok-sm { color: #15803d; font-weight: 600; }

/* The customer order-history tab strip (Quotes / Invoices / ...). It used to borrow
   .admin-nav; it is a tab strip, not an admin nav, so it keeps the original light-background
   rules under its own name. Sharing them would have meant a customer page silently restyling
   itself the next time the admin bar changed colour — which is exactly what was about to
   happen when .admin-nav moved into the dark chrome. */
.tab-nav { display: flex; flex-wrap: wrap; gap: 1.25rem; padding: .5rem 0;
  border-bottom: 1px solid var(--line); margin-bottom: 1rem; font-size: .9rem; }
.tab-nav a { text-decoration: none; font-weight: 600; padding: .15rem 0;
  border-bottom: 2px solid transparent; }
.tab-nav a.current { border-bottom-color: currentColor; }
/* A content section heading in the nav - a label, not a link, because the section
   itself has no landing page (yet). Styled to match .nav-item > a so the bar reads
   as one row. */
.nav-section { font-weight: 600; color: var(--ink); }
.admin-panel { border: 1px solid var(--line); border-radius: 3px; padding: 1rem 1.2rem; margin-bottom: 1.25rem; max-width: 900px; }
.admin-panel h2 { font-size: .85rem; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); margin: 0 0 .75rem; }

/* The branch-only opt-in on the page editor. An accent edge because it is the one control on
   that screen that HIDES PRODUCTS from customers - it should not read like Published or Sort
   order, which are ordinary and reversible-looking. Restrained on purpose though: this is a
   deliberate choice a merchandiser makes, not a warning they have done something wrong. */
.branch-only { border-left: 3px solid var(--warn-line); background: #fdfcf8; }
.branch-only .note, .branch-only .warn { margin: .5rem 0 0; }
.branch-only .warn { color: var(--ink); }
/* .status-grid, .steps and .shortcode-help moved to admin.css.
   .form-embed lives further up, next to the other content-page rules. */
/* --- design center ------------------------------------------------------- */
.lede { font-size: 1rem; max-width: 60ch; }
.design-grid .card img { aspect-ratio: 1; object-fit: cover; }
.design-desc { font-size: .8rem; color: var(--muted); line-height: 1.4;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
.design { display: grid; grid-template-columns: minmax(300px, 640px) 1fr; gap: 2.5rem; align-items: start; }
@media (max-width: 900px) { .design { grid-template-columns: 1fr; } }
.design-media img { border: 1px solid var(--line); border-radius: var(--radius); width: 100%; object-fit: cover; }
/* Open Box tiles - the category card grid plus a price block. */
.openbox-card { display: flex; flex-direction: column; gap: .3rem; }
.openbox-card img { aspect-ratio: 1; object-fit: cover; }
.openbox-price { display: flex; flex-wrap: wrap; align-items: baseline; gap: .4rem; margin-top: .25rem; }
.openbox-price .was { text-decoration: line-through; color: var(--muted); font-size: .85rem; }
.openbox-price .now { font-weight: 700; font-size: 1.05rem; }
.discount-badge { background: #8a1c1c; color: #fff; border-radius: var(--radius);
  padding: .1rem .4rem; font-size: .72rem; font-weight: 600; white-space: nowrap; }
.openbox-saving { font-size: .8rem; color: #8a1c1c; }
.openbox-qty { font-size: .8rem; color: var(--muted); }
.openbox-add { display: flex; gap: .4rem; margin-top: .4rem; }
.openbox-add input { width: 4.5rem; padding: .35rem .4rem; border: 1px solid var(--line);
  border-radius: var(--radius); font: inherit; }
/* Stands where the quantity box and Add to Cart button would be for a signed-in customer, so
   it needs the same top margin the form carried - without it the tile's buttons sit at
   different heights depending on who is looking. */
.openbox-signin { margin-top: .4rem; text-align: center; }

/* Live availability in the product buy table. */
td.avail { font-size: .85rem; line-height: 1.35; }
td.avail .in-stock { font-weight: 600; }
/* .no-stock IS NOT A TYPO FOR .out-stock — both names are live. The product buy table has always
   said .out-stock; the item lists (the cart, a project, Quick Order) say .no-stock, and until
   now that one matched no rule at all, so "0" rendered as ordinary text exactly where it needed
   to be red. Found by deploy/Test-AdminConventions.ps1, which is what that script is for. Styled
   identically rather than renamed: renaming would touch a page this change has no business in. */
td.avail .out-stock, td.avail .no-stock { color: #8a1c1c; font-weight: 600; }
.in-stock { font-weight: 600; }
.no-stock { color: #8a1c1c; font-weight: 600; }
td.avail .future-stock { display: block; color: var(--muted); font-size: .8rem; }
td.avail .note { font-weight: 400; color: var(--muted); }

.openbox-flag { background: #8a1c1c; color: #fff; }
.row-flag.sold-out { display: block; background: #8a1c1c; color: #fff; margin-top: .25rem; }

/* Why a quantity rule changed this line. Its own block so several can stack - a LEVEL_2 line
   can hit a minimum AND a quantity break at once - and quieter than .sold-out, which is an
   error. This is an explanation, not a problem. */
.row-flag.rule-note {
  display: block; margin-top: .25rem; border-color: var(--accent); color: var(--accent);
  white-space: normal; line-height: 1.35;
}

/* The standing rule, as opposed to something that just happened. Muted, because it is context
   rather than news - a line can carry a minimum and a quantity break at once, and neither is a
   change the customer needs to act on. */
.row-flag.rule-hint {
  display: block; margin-top: .25rem; white-space: normal; line-height: 1.35;
}

.visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; }

.design-videos { list-style: none; padding: 0; margin: .75rem 0 0; font-size: .875rem; }
.design-by { font-size: .95rem; }
.design-tags { list-style: none; display: flex; flex-wrap: wrap; gap: .4rem; padding: 0; margin: .75rem 0; }
.design-tags a { display: inline-block; border: 1px solid var(--line); border-radius: var(--radius);
  padding: .2rem .55rem; font-size: .78rem; text-decoration: none; }
.design-description { line-height: 1.6; }
.design-qty { color: var(--muted); }
.shop-look, .designs-using, .designer-designs { margin-top: 3rem; }
.card-unavailable { opacity: .55; }

.designer-search { display: flex; flex-wrap: wrap; gap: 1rem; align-items: flex-end; margin: 1.25rem 0; }
.designer-search label { display: block; font-size: .8rem; }
.designer-search label > span { display: block; font-weight: 600; margin-bottom: .2rem; }
.designer-search input, .designer-search select {
  padding: .45rem .6rem; border: 1px solid var(--line); border-radius: var(--radius); font: inherit; min-width: 12rem; }
.designer-search-actions { display: flex; gap: .75rem; align-items: center; }

.designer-list { list-style: none; padding: 0; margin: 1rem 0 0; display: grid; gap: 1rem; }
.designer-card { display: grid; grid-template-columns: 90px 1fr; gap: 1rem;
  border: 1px solid var(--line); border-radius: var(--radius); padding: 1rem; }
.designer-logo img, .designer-logo .no-image { border: 1px solid var(--line); border-radius: var(--radius); width: 100%; }
/* The size comes from the parent grid column - 90px in a .designer-card, 240px in a
   .designer-profile - and `width: 100%` fills it. So on desktop `.lg` had nothing to do,
   which is why it went unnoticed that it had no rule at all.

   It is not redundant on a phone. Both parents collapse to `grid-template-columns: 1fr`
   under 700px, at which point `width: 100%` stretches a 240px logo across the entire screen,
   upscaled and soft. These caps are the modifier's actual job: never render a logo larger
   than it was uploaded. */
.designer-logo { max-width: 90px; }
.designer-logo.lg { max-width: 240px; }
.designer-body h1, .designer-body h2 { margin: 0 0 .25rem; font-size: 1.1rem; }
.designer-where { font-size: .85rem; color: var(--muted); margin: 0 0 .5rem; }

/* Distance is the answer to the question the visitor asked, so it carries more weight than
   the rest of the line - but it stays inside .designer-where rather than becoming its own
   row, because "Champlin, MN · 12 miles away" reads as one fact. */
.designer-distance { color: var(--ink); font-weight: 600; }
.designer-desc { font-size: .875rem; line-height: 1.5; margin: 0 0 .5rem; }
.designer-profile { display: grid; grid-template-columns: 240px 1fr; gap: 2rem; align-items: start; }
@media (max-width: 700px) { .designer-profile { grid-template-columns: 1fr; } .designer-card { grid-template-columns: 1fr; } }
.designer-contact { list-style: none; padding: 0; margin: .75rem 0 0; display: flex; flex-wrap: wrap; gap: 1rem; font-size: .875rem; }
.designer-tags { margin: .4rem 0; font-size: .8rem; }
.tag-label { color: var(--muted); margin-right: .4rem; }
.badge { display: inline-block; background: #f2f0ec; border: 1px solid var(--line);
  border-radius: 999px; padding: .1rem .55rem; font-size: .75rem; margin-right: .3rem; }
/* ASA's pill vocabulary: .status-pill plus one `s-` variant per state. ASA's variants are
   order statuses (s-paid, s-shipped, ...), which this site has no equivalent of; `s-on` is a
   boolean affirmative - "present", "Custom", "Showing", "Published", "Allowed". Extending
   ASA's convention with one variant beats inventing a second pill system beside it.

   In site.css rather than admin.css because /admin-access and Account/Projects use it, and
   neither loads admin.css. */
.status-pill { display: inline-block; background: #eef2f7; color: #374151;
  border-radius: 999px; padding: .1rem .6rem; font-size: .75rem; font-weight: 600;
  margin-right: .3rem; }
.status-pill.s-on { background: #dcfce7; color: #166534; }

/* "not saved yet", on a staged row and under a staged total.

   IT WAS .note - muted grey, .85rem, the same treatment as "Only you and Vickerman can see
   them". The one line on the page saying YOUR WORK IS NOT STORED YET was styled as an aside,
   and reported as too easy to miss.

   A pill rather than just amber text, because it sits in a table cell beside "Committed: 22"
   and under a currency total, where a run of coloured words reads as more data. A pill reads
   as a state. Same amber the Save button and the staged-change summary already use, so
   "unsaved" is one visual idea on this page rather than three.

   #7c3a06 on --warn-bg, not #b45309: the darker ink clears 4.5:1 against the tint, which
   .warn's colour does not once it is on a filled background rather than white. */
.status-pill.s-unsaved { background: var(--warn-bg); color: #7c3a06; }

/* "Buy Open Box — 60% off", in a buy-table row's availability cell.

   A BUTTON THAT LOOKS LIKE THE OPEN BOX CHIP, because it is the same idea a shopper has already
   met on the Open Box page and on a cart line — #8a1c1c is the red .discount-badge and
   .card-saving already use, so the mark reads the same wherever it appears. It is a real submit
   though, so it takes a pointer, a hover and a visible focus ring: this one spends money.

   display:block and a top margin, because it sits UNDER the stock line rather than beside it —
   the row above it may say "Out of stock", and that sentence and this offer are two separate
   statements about the same item. */
.open-box-chip {
  display: block;
  margin-top: .35rem;
  background: #8a1c1c;
  color: #fff;
  border: 1px solid #8a1c1c;
  border-radius: var(--radius);
  padding: .2rem .5rem;
  font-size: .72rem;
  font-weight: 700;
  cursor: pointer;
  text-align: left;
}
.open-box-chip:hover { background: #6f1616; border-color: #6f1616; }
.open-box-chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.project-actions { display: flex; gap: 1rem; align-items: center; flex-wrap: wrap; margin: .5rem 0; }
/* Checkout: address on the left, money on the right. */
.checkout { display: grid; grid-template-columns: 1.2fr 1fr; gap: 2.5rem; align-items: start; }
@media (max-width: 800px) { .checkout { grid-template-columns: 1fr; } }
.addr-book { display: flex; flex-direction: column; gap: .5rem; margin-bottom: 1rem; }
.addr-option {
  display: block; border: 1px solid var(--line); border-radius: var(--radius);
  padding: .6rem .8rem; cursor: pointer;
}
.addr-option.current { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.addr-option .addr-label { font-weight: 600; display: inline-block; margin-left: .35rem; }
.addr-option .note { display: block; margin-left: 1.4rem; }
.addr-manual { margin-top: 1rem; }
.addr-manual h3 { margin: 0 0 .25rem; font-size: 1rem; }
/* Moves between the saved-address list and the typed form. The two never render together, so
   this link is the only route each way — it is not decoration and should not be tucked away. */
.addr-switch { margin: .75rem 0 0; }
.addr-manual .field { margin: .5rem 0; display: flex; flex-direction: column; gap: .2rem; }
.addr-manual input { padding: .45rem .6rem; border: 1px solid var(--line); border-radius: var(--radius); }
/* Quote and continue sit together — same form, two submits. Wraps rather than squeezing. */
.addr-manual-actions { display: flex; gap: .75rem; align-items: center; flex-wrap: wrap; margin-top: .75rem; }

/* Step 2 -> step 3. Its own rule only so the button clears the summary table above it. */
.checkout-continue { margin: 1rem 0 .5rem; }

/* The carrier and account fields under "Bill another carrier account". Indented so they read as
   belonging to that radio rather than to the group. Deliberately NOT hidden until the radio is
   chosen: hiding them would need script, and docs/JAVASCRIPT.md is clear that enhancement is
   never a dependency - a customer with script disabled must still be able to type an account. */
.tp-other-fields { display: flex; gap: 1.5rem; flex-wrap: wrap; margin: .25rem 0 1rem 1.6rem; }
.tp-other-fields label { margin-bottom: 0; }

/* .admin-form sizes text, email and textarea and stops there, so a date input would sit at the
   browser's own width next to full-width fields. Sized rather than stretched: a date box as wide
   as a notes field reads as though it wants more than a date. */
.input-date { padding: .45rem .6rem; border: 1px solid var(--line); border-radius: var(--radius); font: inherit; }

/* A tax figure that could not be calculated must not read like a number. */
.tax-blocked td { color: #a12; }

/* Exemption prompt. Two weights on purpose: a mismatch (coded exempt, being charged) is a
   warning because the order ships either way if nobody notices; everything else is an offer
   and should not shout. */
.exempt-note, .exempt-warn {
  margin-top: 1rem; padding: .8rem 1rem; border-radius: var(--radius); border: 1px solid var(--line);
}
.exempt-note { background: #faf9f7; }
.exempt-warn { background: var(--warn-bg); border-color: var(--warn-line); }
.exempt-note p, .exempt-warn p { margin: .35rem 0; }

/* .nav-tree, .problems and .col-3 moved to admin.css.

   .inline-form CAME BACK, for the reason written above the admin block at the foot of this
   file: this site's account pages are tabular in a way ASA's retail ones are not, and a row of
   projects needs Archive and Delete sitting beside Open rather than stacked under it. A form is
   a block element, so without this each action lands on its own line and the cell is three
   deep. admin.css is not loaded on /account, so keeping it there would leave the markup
   unstyled — the exact failure deploy/Test-AdminConventions.ps1 was written to catch.
   ASA can keep its copy admin-only; its storefront has no such page. */
.inline-form { display: inline; }

.row-muted { opacity: .55; }
code.small { font-size: .75rem; }

/* Add-an-item box at the foot of the cart. */
.cart-add { border-top: 1px solid var(--line); margin-top: 1.5rem; padding-top: 1rem; max-width: 52rem; }
.cart-add h2 { font-size: 1rem; margin: 0 0 .75rem; }
.cart-add-row { display: flex; gap: .75rem; align-items: flex-end; flex-wrap: wrap; }
.cart-add .field { display: flex; flex-direction: column; gap: .2rem; }
/* The item field takes the room the qty box and the button do not. It was sitting at the
   input's default ~20 characters, which was wide enough for an item number and too narrow
   for the search results hanging under it — the suggest panel is sized to this field, so
   product names were being truncated to "Vickerman Artificial…" with the useful half cut off.
   Grows to fill, wraps to its own line under 30rem rather than squeezing. */
.cart-add .field-grow { flex: 1 1 26rem; min-width: 14rem; }
.cart-add .field-grow input { width: 100%; }
.cart-add label { font-size: .8rem; color: var(--muted); }
.cart-add input { padding: .45rem .6rem; border: 1px solid var(--line); border-radius: var(--radius); font-size: 1rem; }
.cart-add-status { min-height: 1.25rem; margin: .5rem 0 0; font-size: .85rem; }
.cart-add-status.is-ok { color: var(--accent); }
.cart-add-status.is-error { color: #a12; }

/* Brief highlight on a row whose quantity just changed in place. Without a page load this
   is the only cue that the row the buyer was looking at is the one that moved. */
.row-flash { animation: rowflash 1.2s ease-out 1; }
@keyframes rowflash { from { background: var(--warn-bg); } to { background: transparent; } }
@media (prefers-reduced-motion: reduce) { .row-flash { animation: none; } }

.plain-list { list-style: none; margin: 0 0 .5rem; padding: 0; }
.plain-list li { margin: .2rem 0; }

/* Fade a long block to three rendered lines and stop. Counting LINES rather than characters
   is the point: the same clamp is right at every window width, and the whole string stays in
   the DOM for browser find-in-page. */
.clamp-3 {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Without these the description column takes whatever it likes and squeezes the project
   name into a three-line stack, which is how "Omni Dallas - Hops and Hens" ended up taller
   than its own row needed to be. */
.col-name { width: 16%; min-width: 9rem; }
.col-desc { width: 44%; }

/* ---------------------------------------------------------------------------
   Sign-in, password reset.

   A narrow single-column form on a wide page. Deliberately capped at 26rem rather than
   filling the container: a login form stretched across a 1600px monitor puts the two fields
   a hand-span apart and reads as broken.
--------------------------------------------------------------------------- */
.auth-page { max-width: 26rem; margin: 2rem auto; }
.auth-page h1 { margin-top: 0; }

.auth-form { display: flex; flex-direction: column; gap: 1rem; margin: 1.25rem 0; }
.auth-form .field { display: flex; flex-direction: column; gap: .25rem; }
.auth-form label { font-weight: 600; font-size: .85rem; }

/* 1rem, not smaller. iOS Safari zooms the whole page when a focused input's text is under
   16px, and the resulting jump on a login form looks like a bug. */
.auth-form input[type="email"],
.auth-form input[type="password"] {
  padding: .5rem .6rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  font-size: 1rem;
  font-family: inherit;
}

.auth-form .field-check { flex-direction: row; align-items: center; flex-wrap: wrap; gap: .4rem; }
.auth-form .field-check label { font-weight: 400; }
.auth-form .field-check .hint { flex-basis: 100%; }

.hint { color: var(--muted); font-size: .8rem; }

.auth-alt { font-size: .9rem; }

/* .note carries the muted style already; these three make a status actually look like one.
   Without them a failed sign-in renders as small grey text under the heading, which is where
   nobody looks after pressing a button. */
.note.error, .note.warn, .note.ok {
  padding: .6rem .9rem;
  border-radius: var(--radius);
  font-size: .9rem;
  color: var(--ink);
}
.note.error { background: #fdecea; border: 1px solid #b3261e; }
.note.warn  { background: var(--warn-bg); border: 1px solid var(--warn-line); }
.note.ok    { background: #eaf5ef; border: 1px solid var(--accent); }

.signin-link { text-decoration: none; font-weight: 600; color: var(--ink); white-space: nowrap; }

/* Sign out sits below the account links with a rule above it — it is the one entry in that menu
   that ends something, and it should not be a click away from "Orders & Invoices". */
.nav-sub-sep { border-top: 1px solid var(--line); margin-top: .3rem; padding-top: .3rem; }
.signout-form { margin: 0; }

/* Deliberately a copy of .nav-sub a, not an override of .btn-link. Sign out is a menu row like
   the four above it and should be indistinguishable from them; it is only a <button> because the
   POST requires a form. .btn-link's padding: 0 is what left it flush against the panel edge, and
   its underline and accent colour made the one destructive entry the loudest thing in the menu.
   Full width so the hover highlight covers the row rather than just the words. */
.signout-form .btn-link {
  display: block; width: 100%; text-align: left; border: 0; background: none; cursor: pointer;
  padding: .35rem 1rem;
  font: inherit; font-size: .9rem; color: var(--ink); text-decoration: none;
}
.signout-form .btn-link:hover { background: #faf9f7; color: var(--accent); }

/* Has to live here, next to the rule it overrides, NOT in the nav media query up at .nav-sub —
   a media query adds no specificity, so the same selector further down the file would win at
   every width and the row would keep its 1rem inset after the panel had lost its own. */
@media (max-width: 700px) {
  .signout-form .btn-link { padding: .3rem 0; }
}

/* ---------------------------------------------------------------------------
   Decorating calculators — garland, ornament, wreath ornament.

   ONE SET OF RULES FOR THREE PAGES. They are the same screen with different numbers in it:
   inputs on the left, an illustration and a coverage reading on the right, a disclaimer under
   both. Three near-identical blocks would drift, and the originals on the Orchard site proved
   it — each carried its own copy of the same six-rule <style> block, and they had already
   stopped matching.

   The inputs column borrows .admin-form for its fieldsets, labels and .field-row, which is the
   same borrowing the designer profile editor does and for the same reason: it is the site's
   form vocabulary, not an admin one. See the note above that block.
--------------------------------------------------------------------------- */
.calc { display: grid; grid-template-columns: minmax(320px, 1fr) 1fr; gap: 2.5rem; align-items: start; margin: 1.5rem 0; }
@media (max-width: 900px) { .calc { grid-template-columns: 1fr; } }

.calc-inputs { max-width: none; }   /* .admin-form's 760px cap is the grid's job here */
.calc-inputs fieldset { margin-bottom: 1rem; }
.calc-inputs label { margin-bottom: .75rem; }

/* .admin-form styles text and textarea; every field on these pages is a number. */
.calc-inputs input[type=number] {
  width: 100%; padding: .45rem .6rem; border: 1px solid var(--line);
  border-radius: var(--radius); font: inherit;
}

.calc-actions { display: flex; gap: .75rem; align-items: center; flex-wrap: wrap; }

/* The size/quantity grid. .admin-table gives it the borders and the header; these three make
   the quantity column narrow so twelve rows read as a list rather than a spreadsheet. */
.calc-sizes { margin-top: .25rem; }
.calc-sizes th[scope=row] { font-weight: 400; width: 30%; }
.calc-qty { width: 6rem; padding: .35rem .5rem; border: 1px solid var(--line);
  border-radius: var(--radius); font: inherit; }
.calc-pack { color: var(--muted); font-size: .8rem; white-space: nowrap; }

/* Sticks while a long size list scrolls past it — the picture is the answer, and scrolling to
   row twelve should not scroll it off. Only while there are two columns: once the layout stacks,
   the picture is already directly above the answer and sticking it would peel it off the page. */
.calc-visual { text-align: center; }
@media (min-width: 901px) { .calc-visual { position: sticky; top: 1rem; } }
.calc-visual h2 { font-size: 1rem; margin: 0 0 .5rem; }
.calc-img { max-width: 100%; height: auto; }

.calc-meter { width: 100%; max-width: 24rem; height: 1rem; margin-top: .5rem; }
.calc-coverage { font-size: .9rem; margin: .35rem 0 0; }

/* The garland's answer. Larger than body copy because it IS the answer, and it sits where the
   other two put their coverage reading — under the picture — so all three read the same way. */
.calc-result { font-size: 1.05rem; line-height: 1.5; max-width: 60ch; margin: .75rem auto 0;
  min-height: 1.5rem; text-align: left; }

.calc-hint { color: var(--muted); font-size: .8rem; }

.calc-disclaimer { margin-top: 3rem; border-top: 1px solid var(--line); padding-top: 1rem;
  max-width: 68ch; color: var(--muted); font-size: .85rem; }
.calc-disclaimer h2 { font-size: .9rem; margin: 0 0 .35rem; color: var(--ink); }

/* ---------------------------------------------------------------------------
   Ornament recipe (/ornamentrecipe)

   The same two-column shape as the calculators above, and it reuses .calc-actions, .calc-qty,
   .calc-hint and .calc-disclaimer rather than restating them — it IS one of the calculators as
   far as the eye is concerned, it just answers with a table instead of a picture. What it needs
   of its own is the colour blocks, which nothing else on the site has.

   .recipe-hidden rather than removing the nodes: the results table, the totals line, the gaps
   panel and the warning all exist in the markup from the first render so that
   wwwroot/js/ornament-recipe.js has somewhere to write. Building them in script would mean the
   no-script page and the live one were two different pages.
--------------------------------------------------------------------------- */
.recipe { display: grid; grid-template-columns: minmax(340px, 2fr) 3fr; gap: 2.5rem;
  align-items: start; margin: 1.5rem 0; }
@media (max-width: 1000px) { .recipe { grid-template-columns: 1fr; } }

.recipe-hidden { display: none; }

.recipe-inputs { max-width: none; }
.recipe-inputs fieldset { margin-bottom: 1.25rem; }
.recipe-inputs input[type=number] { width: 100%; padding: .45rem .6rem; border: 1px solid var(--line);
  border-radius: var(--radius); font: inherit; }
.recipe-inputs select { width: 100%; padding: .45rem .6rem; border: 1px solid var(--line);
  border-radius: var(--radius); font: inherit; background: var(--bg); }

.recipe-sizes { margin-top: .25rem; }
.recipe-sizes th[scope=row] { font-weight: 400; width: 45%; }

/* The quantity boxes, sized rather than stretched, for the same reason as the percentages below.
   .calc-qty already asks for 6rem and loses to the width: 100% above it — on the calculator that
   goes unnoticed, because its table has a third column and the cell is narrow anyway. This table
   has two columns, so the same override stretched a three-digit box across half the panel. */
.recipe-inputs .recipe-sizes input[type=number] { width: 6rem; }

/* One colour of the scheme. Boxed because a block is a unit — the percentages inside it are
   shares of the colour above them, not of the order, and the border is what says so. */
.recipe-color { border: 1px solid var(--line); border-radius: var(--radius); padding: .9rem;
  margin-bottom: .9rem; }
.recipe-color-head { display: grid; grid-template-columns: 1fr auto auto; gap: .75rem;
  align-items: end; }
@media (max-width: 520px) { .recipe-color-head { grid-template-columns: 1fr; align-items: stretch; } }
.recipe-color-head label { margin-bottom: 0; }

/* A percentage box and its sign on one line.

   THE BOX IS SIZED, NOT STRETCHED, and that is the whole trick. These inputs inherit
   width: 100% from .recipe-inputs above, which fills the column and leaves the % nothing to sit
   in - and flex cannot rescue it, because an input's automatic minimum size is its intrinsic
   width and it will not shrink below that to make room. So the box gets a width of its own,
   wide enough for "100" and the spinner and no wider, and the columns beside it size to content
   rather than to a guess.

   5rem rather than the tightest fit. Of the 80px, the padding and border take 21 and the spinner
   takes about 17, leaving roughly 42 for a three-digit "100" that needs about 27 - so a browser
   whose spinner is wider than Chrome's still has room. Shaving it to 4.5rem also fits on paper
   and clips the value on the first browser that disagrees, which is not a trade worth making for
   half a centimetre.

   The selector carries .recipe-inputs only to outrank that 100% - the two would otherwise have
   equal specificity and this would depend on staying below it in the file. */
.recipe-pct { display: flex; align-items: center; gap: .3rem; }
.recipe-inputs .recipe-pct input[type=number] { width: 5rem; flex: 0 0 auto; }

.recipe-finishes { margin: .9rem 0 0; padding: .75rem 0 0; border: 0;
  border-top: 1px solid var(--line); }
.recipe-finishes legend { font-size: .8rem; color: var(--muted); padding: 0; }
/* Finish name on the left, its percentage on the right.

   TWO CLASSES, and the second one is not decoration. .admin-form sets `display: block` on its
   labels at (0,1,1), which outranks a lone .recipe-finish at (0,1,0) — so the grid never applied
   and every finish row rendered as a name stacked above its box, seven times per colour. It
   looked deliberate enough to survive a read of the markup and only showed up on screen. */
.recipe-inputs .recipe-finish { display: grid; grid-template-columns: 1fr auto; gap: .5rem;
  align-items: center; margin-bottom: .4rem; font-size: .9rem; }

/* min-width: 0, and it is doing real work rather than declaring the class.

   A grid item's automatic minimum size is its MIN-CONTENT size, so this column would refuse to
   be narrower than the widest thing in it — and the widest thing in it is a five-column table of
   item numbers and full catalogue descriptions. The 3fr track would be pushed out to fit, the
   whole page would scroll sideways on a phone, and .recipe-table-scroll's overflow-x below would
   never fire, because an element that was never squeezed has nothing to scroll. */
.recipe-results { min-width: 0; }

/* Five columns of item numbers do not fit a phone and must not widen the page — the table
   scrolls inside this box instead. */
.recipe-table-scroll { overflow-x: auto; }
.recipe-lines { margin-top: .5rem; }
.recipe-lines code { font-size: .85rem; }

.recipe-totals { margin: .75rem 0 0; font-weight: 600; }
.recipe-empty { margin: .75rem 0 0; }

.recipe-gaps { margin-top: 1.5rem; border-left: 3px solid var(--warn-line); padding-left: .9rem; }
.recipe-gaps h3 { font-size: .95rem; margin: 0 0 .25rem; }
.recipe-gaps ul { margin: .5rem 0 0; padding-left: 1.1rem; font-size: .9rem; }
.recipe-gaps li { margin-bottom: .2rem; }

.recipe-exports { margin-top: 1.25rem; }

/* --- brand icons ----------------------------------------------------------
   Social marks as font-like glyphs: <i class="icon-instagram"></i>, sized with font-size and
   coloured with color, exactly as an icon font behaves.

   NO ICON FONT, AND NO THIRD-PARTY REQUEST. Font Awesome was asked for; the site deliberately
   loads nothing from anywhere else - no CDN, no web fonts - and that property is worth more
   than the convenience. Self-hosting a font would have satisfied the rule but costs a font
   file, a library to version, and a licence to carry, for four glyphs.

   MASK, NOT BACKGROUND. background-image would paint each mark in whatever colour the SVG was
   drawn in and ignore `color` entirely. A mask uses the artwork as a stencil over
   background-color: currentColor, so the icon inherits the surrounding text colour and
   survives a theme change or a dark footer. It is what makes these behave like glyphs rather
   than pictures.

   URL-ENCODED, NOT BASE64: readable in the stylesheet, and about a quarter smaller. Generated
   from the official brand SVGs in assets/brand/ - do not hand-edit the path data, replace the
   source file and regenerate.

   Marks are the platforms' own; they are trademarks and are used unaltered in shape. Rendering
   monochrome is the standard treatment every icon set uses and is what the four-in-a-row look
   requires - but do not stretch, rotate or reproportion them. */
.icon-instagram, .icon-facebook, .icon-pinterest, .icon-tiktok {
  display: inline-block;
  width: 1em; height: 1em;
  vertical-align: -0.125em;      /* sits on the text baseline rather than riding above it */
  background-color: currentColor;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center;  mask-position: center;
  -webkit-mask-size: contain;     mask-size: contain;
}

/* A row of social links, for the footer. Touch targets rather than bare 16px glyphs: these end
   up on a phone, where a 1em icon with no padding is an unhittable target. */
.social-row { display: flex; flex-wrap: wrap; gap: .25rem; align-items: center; list-style: none; padding: 0; margin: .5rem 0 0; }
.social-row a { display: inline-flex; align-items: center; justify-content: center; padding: .55rem; font-size: 1.25rem; color: inherit; text-decoration: none; border-radius: var(--radius); }
.social-row a:hover { color: var(--accent); background: rgb(0 0 0 / .04); }
.icon-instagram { -webkit-mask-image: url("data:image/svg+xml,%3Csvg role=%22img%22 viewBox=%220 0 24 24%22 xmlns=%22http://www.w3.org/2000/svg%22%3E%3Cpath d=%22M7.0301.084c-1.2768.0602-2.1487.264-2.911.5634-.7888.3075-1.4575.72-2.1228 1.3877-.6652.6677-1.075 1.3368-1.3802 2.127-.2954.7638-.4956 1.6365-.552 2.914-.0564 1.2775-.0689 1.6882-.0626 4.947.0062 3.2586.0206 3.6671.0825 4.9473.061 1.2765.264 2.1482.5635 2.9107.308.7889.72 1.4573 1.388 2.1228.6679.6655 1.3365 1.0743 2.1285 1.38.7632.295 1.6361.4961 2.9134.552 1.2773.056 1.6884.069 4.9462.0627 3.2578-.0062 3.668-.0207 4.9478-.0814 1.28-.0607 2.147-.2652 2.9098-.5633.7889-.3086 1.4578-.72 2.1228-1.3881.665-.6682 1.0745-1.3378 1.3795-2.1284.2957-.7632.4966-1.636.552-2.9124.056-1.2809.0692-1.6898.063-4.948-.0063-3.2583-.021-3.6668-.0817-4.9465-.0607-1.2797-.264-2.1487-.5633-2.9117-.3084-.7889-.72-1.4568-1.3876-2.1228C21.2982 1.33 20.628.9208 19.8378.6165 19.074.321 18.2017.1197 16.9244.0645 15.6471.0093 15.236-.005 11.977.0014 8.718.0076 8.31.0215 7.0301.0839m.1402 21.6932c-1.17-.0509-1.8053-.2453-2.2287-.408-.5606-.216-.96-.4771-1.3819-.895-.422-.4178-.6811-.8186-.9-1.378-.1644-.4234-.3624-1.058-.4171-2.228-.0595-1.2645-.072-1.6442-.079-4.848-.007-3.2037.0053-3.583.0607-4.848.05-1.169.2456-1.805.408-2.2282.216-.5613.4762-.96.895-1.3816.4188-.4217.8184-.6814 1.3783-.9003.423-.1651 1.0575-.3614 2.227-.4171 1.2655-.06 1.6447-.072 4.848-.079 3.2033-.007 3.5835.005 4.8495.0608 1.169.0508 1.8053.2445 2.228.408.5608.216.96.4754 1.3816.895.4217.4194.6816.8176.9005 1.3787.1653.4217.3617 1.056.4169 2.2263.0602 1.2655.0739 1.645.0796 4.848.0058 3.203-.0055 3.5834-.061 4.848-.051 1.17-.245 1.8055-.408 2.2294-.216.5604-.4763.96-.8954 1.3814-.419.4215-.8181.6811-1.3783.9-.4224.1649-1.0577.3617-2.2262.4174-1.2656.0595-1.6448.072-4.8493.079-3.2045.007-3.5825-.006-4.848-.0608M16.953 5.5864A1.44 1.44 0 1 0 18.39 4.144a1.44 1.44 0 0 0-1.437 1.4424M5.8385 12.012c.0067 3.4032 2.7706 6.1557 6.173 6.1493 3.4026-.0065 6.157-2.7701 6.1506-6.1733-.0065-3.4032-2.771-6.1565-6.174-6.1498-3.403.0067-6.156 2.771-6.1496 6.1738M8 12.0077a4 4 0 1 1 4.008 3.9921A3.9996 3.9996 0 0 1 8 12.0077%22/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg role=%22img%22 viewBox=%220 0 24 24%22 xmlns=%22http://www.w3.org/2000/svg%22%3E%3Cpath d=%22M7.0301.084c-1.2768.0602-2.1487.264-2.911.5634-.7888.3075-1.4575.72-2.1228 1.3877-.6652.6677-1.075 1.3368-1.3802 2.127-.2954.7638-.4956 1.6365-.552 2.914-.0564 1.2775-.0689 1.6882-.0626 4.947.0062 3.2586.0206 3.6671.0825 4.9473.061 1.2765.264 2.1482.5635 2.9107.308.7889.72 1.4573 1.388 2.1228.6679.6655 1.3365 1.0743 2.1285 1.38.7632.295 1.6361.4961 2.9134.552 1.2773.056 1.6884.069 4.9462.0627 3.2578-.0062 3.668-.0207 4.9478-.0814 1.28-.0607 2.147-.2652 2.9098-.5633.7889-.3086 1.4578-.72 2.1228-1.3881.665-.6682 1.0745-1.3378 1.3795-2.1284.2957-.7632.4966-1.636.552-2.9124.056-1.2809.0692-1.6898.063-4.948-.0063-3.2583-.021-3.6668-.0817-4.9465-.0607-1.2797-.264-2.1487-.5633-2.9117-.3084-.7889-.72-1.4568-1.3876-2.1228C21.2982 1.33 20.628.9208 19.8378.6165 19.074.321 18.2017.1197 16.9244.0645 15.6471.0093 15.236-.005 11.977.0014 8.718.0076 8.31.0215 7.0301.0839m.1402 21.6932c-1.17-.0509-1.8053-.2453-2.2287-.408-.5606-.216-.96-.4771-1.3819-.895-.422-.4178-.6811-.8186-.9-1.378-.1644-.4234-.3624-1.058-.4171-2.228-.0595-1.2645-.072-1.6442-.079-4.848-.007-3.2037.0053-3.583.0607-4.848.05-1.169.2456-1.805.408-2.2282.216-.5613.4762-.96.895-1.3816.4188-.4217.8184-.6814 1.3783-.9003.423-.1651 1.0575-.3614 2.227-.4171 1.2655-.06 1.6447-.072 4.848-.079 3.2033-.007 3.5835.005 4.8495.0608 1.169.0508 1.8053.2445 2.228.408.5608.216.96.4754 1.3816.895.4217.4194.6816.8176.9005 1.3787.1653.4217.3617 1.056.4169 2.2263.0602 1.2655.0739 1.645.0796 4.848.0058 3.203-.0055 3.5834-.061 4.848-.051 1.17-.245 1.8055-.408 2.2294-.216.5604-.4763.96-.8954 1.3814-.419.4215-.8181.6811-1.3783.9-.4224.1649-1.0577.3617-2.2262.4174-1.2656.0595-1.6448.072-4.8493.079-3.2045.007-3.5825-.006-4.848-.0608M16.953 5.5864A1.44 1.44 0 1 0 18.39 4.144a1.44 1.44 0 0 0-1.437 1.4424M5.8385 12.012c.0067 3.4032 2.7706 6.1557 6.173 6.1493 3.4026-.0065 6.157-2.7701 6.1506-6.1733-.0065-3.4032-2.771-6.1565-6.174-6.1498-3.403.0067-6.156 2.771-6.1496 6.1738M8 12.0077a4 4 0 1 1 4.008 3.9921A3.9996 3.9996 0 0 1 8 12.0077%22/%3E%3C/svg%3E"); }
.icon-facebook { -webkit-mask-image: url("data:image/svg+xml,%3Csvg role=%22img%22 viewBox=%220 0 24 24%22 xmlns=%22http://www.w3.org/2000/svg%22%3E%3Cpath d=%22M9.101 23.691v-7.98H6.627v-3.667h2.474v-1.58c0-4.085 1.848-5.978 5.858-5.978.401 0 .955.042 1.468.103a8.68 8.68 0 0 1 1.141.195v3.325a8.623 8.623 0 0 0-.653-.036 26.805 26.805 0 0 0-.733-.009c-.707 0-1.259.096-1.675.309a1.686 1.686 0 0 0-.679.622c-.258.42-.374.995-.374 1.752v1.297h3.919l-.386 2.103-.287 1.564h-3.246v8.245C19.396 23.238 24 18.179 24 12.044c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.628 3.874 10.35 9.101 11.647Z%22/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg role=%22img%22 viewBox=%220 0 24 24%22 xmlns=%22http://www.w3.org/2000/svg%22%3E%3Cpath d=%22M9.101 23.691v-7.98H6.627v-3.667h2.474v-1.58c0-4.085 1.848-5.978 5.858-5.978.401 0 .955.042 1.468.103a8.68 8.68 0 0 1 1.141.195v3.325a8.623 8.623 0 0 0-.653-.036 26.805 26.805 0 0 0-.733-.009c-.707 0-1.259.096-1.675.309a1.686 1.686 0 0 0-.679.622c-.258.42-.374.995-.374 1.752v1.297h3.919l-.386 2.103-.287 1.564h-3.246v8.245C19.396 23.238 24 18.179 24 12.044c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.628 3.874 10.35 9.101 11.647Z%22/%3E%3C/svg%3E"); }
.icon-pinterest { -webkit-mask-image: url("data:image/svg+xml,%3Csvg role=%22img%22 viewBox=%220 0 24 24%22 xmlns=%22http://www.w3.org/2000/svg%22%3E%3Cpath d=%22M12.017 0C5.396 0 .029 5.367.029 11.987c0 5.079 3.158 9.417 7.618 11.162-.105-.949-.199-2.403.041-3.439.219-.937 1.406-5.957 1.406-5.957s-.359-.72-.359-1.781c0-1.663.967-2.911 2.168-2.911 1.024 0 1.518.769 1.518 1.688 0 1.029-.653 2.567-.992 3.992-.285 1.193.6 2.165 1.775 2.165 2.128 0 3.768-2.245 3.768-5.487 0-2.861-2.063-4.869-5.008-4.869-3.41 0-5.409 2.562-5.409 5.199 0 1.033.394 2.143.889 2.741.099.12.112.225.085.345-.09.375-.293 1.199-.334 1.363-.053.225-.172.271-.401.165-1.495-.69-2.433-2.878-2.433-4.646 0-3.776 2.748-7.252 7.92-7.252 4.158 0 7.392 2.967 7.392 6.923 0 4.135-2.607 7.462-6.233 7.462-1.214 0-2.354-.629-2.758-1.379l-.749 2.848c-.269 1.045-1.004 2.352-1.498 3.146 1.123.345 2.306.535 3.55.535 6.607 0 11.985-5.365 11.985-11.987C23.97 5.39 18.592.026 11.985.026L12.017 0z%22/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg role=%22img%22 viewBox=%220 0 24 24%22 xmlns=%22http://www.w3.org/2000/svg%22%3E%3Cpath d=%22M12.017 0C5.396 0 .029 5.367.029 11.987c0 5.079 3.158 9.417 7.618 11.162-.105-.949-.199-2.403.041-3.439.219-.937 1.406-5.957 1.406-5.957s-.359-.72-.359-1.781c0-1.663.967-2.911 2.168-2.911 1.024 0 1.518.769 1.518 1.688 0 1.029-.653 2.567-.992 3.992-.285 1.193.6 2.165 1.775 2.165 2.128 0 3.768-2.245 3.768-5.487 0-2.861-2.063-4.869-5.008-4.869-3.41 0-5.409 2.562-5.409 5.199 0 1.033.394 2.143.889 2.741.099.12.112.225.085.345-.09.375-.293 1.199-.334 1.363-.053.225-.172.271-.401.165-1.495-.69-2.433-2.878-2.433-4.646 0-3.776 2.748-7.252 7.92-7.252 4.158 0 7.392 2.967 7.392 6.923 0 4.135-2.607 7.462-6.233 7.462-1.214 0-2.354-.629-2.758-1.379l-.749 2.848c-.269 1.045-1.004 2.352-1.498 3.146 1.123.345 2.306.535 3.55.535 6.607 0 11.985-5.365 11.985-11.987C23.97 5.39 18.592.026 11.985.026L12.017 0z%22/%3E%3C/svg%3E"); }
.icon-tiktok { -webkit-mask-image: url("data:image/svg+xml,%3Csvg role=%22img%22 viewBox=%220 0 24 24%22 xmlns=%22http://www.w3.org/2000/svg%22%3E%3Cpath d=%22M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z%22/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg role=%22img%22 viewBox=%220 0 24 24%22 xmlns=%22http://www.w3.org/2000/svg%22%3E%3Cpath d=%22M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z%22/%3E%3C/svg%3E"); }

