/* ═══════════════════════════════════════════════════════════════════════════
   STITCH OBSESSION — ARTICLE PAGE
   v1 · 2026-08-12 · written from scratch onto the "Obsession Press" system

   This file styles ONLY what is particular to a pattern page: the action bar,
   the hero plate, the pattern card, the materials and round lists, the
   progress tracker, the abbreviations drawer, the Q&A accordion, the modals
   and the print sheet. Everything shared — the masthead, bands, panels,
   buttons, forms, footer, ornament engine — comes from stitchobsession.css and
   is NOT repeated here.

   ── LOAD ORDER (this matters) ─────────────────────────────────────────────
   article.html links THIS file FIRST and stitchobsession.css SECOND. On an
   equal-specificity tie the design system wins. So every rule below that has
   to beat a system rule is deliberately written one level more specific —
   each of those is marked "⤷ beats stitchobsession.css". Do not "simplify" them
   back to a bare class selector; the style silently reverts.

   ── CONTRAST ──────────────────────────────────────────────────────────────
   Ratios quoted are against the ground the element actually sits on. Body and
   UI text clears 4.5:1; 3:1 is used only at ≥24px display sizes. Mustard is a
   ground, never a foreground: ink on mustard is 7.19:1.

   ── ORDER ─────────────────────────────────────────────────────────────────
    1 Loading + shell        6 Rounds + progress     11 Modals
    2 Hero + action bar      7 Abbreviations         12 Floating progress
    3 At-a-glance            8 Q&A accordion         13 Footer additions
    4 Content cards          9 Newsletter            14 Responsive
    5 Pattern card          10 Attribution           15 Reduced motion + print
   ═══════════════════════════════════════════════════════════════════════ */


/* ═══════════════════════════════════════════════════════════════════════════
   1 · LOADING + SHELL
   ═══════════════════════════════════════════════════════════════════════ */
.sp-loading{
  display:grid; place-items:center;
  min-height:70vh; padding:var(--sp-8) var(--gutter);
}
.sp-loading-inner{ text-align:center; }
.sp-loading-mark{
  display:inline-block; font-size:3.6rem; line-height:1;
  filter:drop-shadow(3px 3px 0 var(--ink));
  animation:sp-tumble 2.4s cubic-bezier(.5,.05,.5,.95) infinite;
}
.sp-loading-text{
  margin:var(--sp-4) 0 0;
  font-family:var(--caps); font-weight:600; font-size:var(--fs-sm);
  text-transform:uppercase; letter-spacing:var(--track-wide);
  color:var(--ink-2);                                    /* 6.71:1 ✓ AA */
}
@keyframes sp-tumble{
  0%,100%{ rotate:-12deg; translate:0 0; }
  50%    { rotate:12deg;  translate:0 -10px; }
}

/* Scallop strips need TWO colours: the bumps take the band below, the flat
   ground behind them must take the band above or the seam shows the page
   background through it.
   MOVED: this now lives in stitchobsession.css §5.2 as .sp-scallop--on-*,
   which also draws an ink rim on the disc so the cut reads between two
   papers that are four percent apart. Declaring background-color here as
   well was a live hazard, not just duplication: article.css loads BEFORE
   stitchobsession.css, so .sp-scallop{background-color:var(--scallop-bg)}
   won the equal-specificity tie and would have blanked these seams. One
   system, one file. */

/* Locks the page while a modal or the drawer is open. */
html.sp-layer-open,
html.sp-layer-open body{ overflow:hidden; }

.sp-404-wrap{ padding-block:var(--sp-8); }


/* ═══════════════════════════════════════════════════════════════════════════
   2 · HERO + PATTERN ACTION BAR
   ═══════════════════════════════════════════════════════════════════════ */
.hero-inner{ position:relative; z-index:1; }

/* ⤷ beats stitchobsession.css .hero-title{max-width:16ch} — pattern titles are
   long ("3D Popcorn Flower Granny Square — Free Crochet Pattern") and 16ch
   shredded them into six lines. */
.hero-section .hero-title{ max-width:20ch; margin-bottom:var(--sp-4); }

/* ⤷ beats stitchobsession.css .decorative-elements{position:absolute;inset:0;
   z-index:0;pointer-events:none}. In the system that class is a floating
   doodle LAYER. Here the same element holds the three hero pills, which are
   real content in the flow — left absolute and inset to 0 they stretched into
   full-height bars across the hero. Both class names must be on the selector
   or the system's rule wins the tie. */
.decorative-elements.hero-tags{
  position:static; inset:auto; z-index:auto; pointer-events:auto;
  display:flex; flex-wrap:wrap; align-items:center; gap:var(--sp-2);
  margin-bottom:var(--sp-4);
}
.hero-kicker{ margin:0 0 var(--sp-1); }
.article-category-badge:empty{ display:none; }
.article-desc:empty{ display:none; }

/* ── The four pattern actions ─────────────────────────────────────────── */
.pattern-action-bar{
  display:flex; flex-wrap:wrap; gap:var(--sp-3);
  margin:var(--sp-5) 0 var(--sp-3);
}
.pab-btn{
  display:inline-flex; align-items:center; justify-content:center; gap:var(--sp-2);
  min-height:52px;
  padding:12px var(--sp-4);
  font-family:var(--caps); font-weight:600; font-size:var(--fs-sm);
  text-transform:uppercase; letter-spacing:var(--track-caps);
  text-decoration:none; cursor:pointer;
  color:var(--ink);                                      /* 13.90:1 ✓ AAA */
  background:var(--paper-light);
  border:var(--bw) solid var(--line);
  border-radius:var(--r-sm);
  box-shadow:var(--pop-sm);
  rotate:var(--tilt, -1deg);
  transition:translate var(--t-fast) var(--ease),
             box-shadow var(--t-fast) var(--ease),
             rotate var(--t) var(--ease),
             background-color var(--t-fast) var(--ease);
}
.pattern-action-bar > *:nth-child(2n){ --tilt:1deg; }
.pab-btn:hover{ rotate:0deg; translate:-2px -2px; box-shadow:var(--pop); color:var(--ink); }
.pab-btn:active{ rotate:0deg; translate:var(--press) var(--press); box-shadow:var(--pop-in); }
.pab-btn:focus-visible{ outline:3px solid var(--focus); outline-offset:3px; rotate:0deg; }
.pab-icon{ display:grid; place-items:center; width:20px; height:20px; flex:none; }
.pab-label{ white-space:nowrap; }

.pab-solid{ background:var(--red);  color:var(--on-red); }   /* 4.96:1 ✓ AA */
.pab-solid:hover{ background:var(--red-d); color:var(--on-red); }
.pab-pin{ background:var(--ink); color:var(--on-ink); }      /* 12.53:1 ✓ AAA */
.pab-pin:hover{ background:#3c2e21; color:var(--on-ink); }
.pab-outline{ background:var(--paper-light); }
.pab-outline:hover{ background:var(--cream-deep); }
.pab-jump{ background:var(--teal); color:var(--on-teal); }   /* 5.90:1 ✓ AA */
.pab-jump:hover{ background:var(--teal-d); color:var(--on-teal); }

.pab-toast{
  display:inline-block;
  margin:0 0 var(--sp-4);
  padding:8px var(--sp-4);
  font-family:var(--caps); font-weight:600; font-size:var(--fs-xs);
  text-transform:uppercase; letter-spacing:var(--track-caps);
  color:var(--on-mustard); background:var(--mustard);       /* 7.19:1 ✓ AAA */
  border:var(--bw-hair) solid var(--line);
  border-radius:var(--r-pill);
  box-shadow:var(--pop-sm);
  opacity:0; translate:0 6px; pointer-events:none;
  transition:opacity var(--t) var(--ease), translate var(--t) var(--ease);
}
.pab-toast:empty{ display:none; }
.pab-toast.show{ opacity:1; translate:0 0; }

/* ── Hero photo plate ─────────────────────────────────────────────────── */
.hero-plate{
  position:relative;
  margin:var(--sp-5) 0 var(--sp-6);
  max-width:760px;
}
.hero-roundel{
  position:absolute; z-index:5;
  top:calc(-1 * var(--sp-5)); right:calc(-1 * var(--sp-4));
  pointer-events:none;
}
.hero-image-grid-wrap:empty{ display:none; }
.hero-thumbnail-grid{
  display:grid; gap:var(--sp-3);
  grid-template-columns:1fr;
}
.hero-thumbnail-grid[data-count="4"]{ grid-template-columns:repeat(2, minmax(0,1fr)); }
.hero-thumbnail-grid[data-count="2"],
.hero-thumbnail-grid[data-count="3"]{ grid-template-columns:repeat(2, minmax(0,1fr)); }

.hero-tile,
.why-love-tile{
  position:relative; display:block; overflow:hidden;
  background:var(--cream-deep);
  border:var(--bw-thick) solid var(--line);
  border-radius:var(--r);
  box-shadow:var(--pop-lg);
  rotate:var(--tilt, -1.2deg);
  transition:rotate var(--t) var(--ease),
             translate var(--t) var(--ease),
             box-shadow var(--t) var(--ease);
}
.hero-thumbnail-grid > *:nth-child(2n),
.why-love-images-grid > *:nth-child(2n){ --tilt:1.2deg; }
.hero-tile:hover,
.why-love-tile:hover{ rotate:0deg; translate:0 -4px; box-shadow:var(--pop-xl); }
.hero-tile:focus-visible,
.why-love-tile:focus-visible{ outline:3px solid var(--focus); outline-offset:4px; rotate:0deg; }
.hero-tile img,
.why-love-tile img{
  display:block; width:100%; height:100%;
  aspect-ratio:4/3; object-fit:cover;
}
.hero-thumbnail-grid[data-count="4"] .hero-tile img{ aspect-ratio:1; }

/* The magnifier flash in the corner of every enlargeable photo. */
.hero-tile-zoom{
  position:absolute; z-index:2; right:10px; bottom:10px;
  display:grid; place-items:center;
  width:38px; height:38px;
  background:var(--mustard); color:var(--on-mustard);
  border:var(--bw) solid var(--line); border-radius:50%;
  box-shadow:var(--pop-sm);
  opacity:0; scale:.8;
  transition:opacity var(--t) var(--ease), scale var(--t) var(--ease);
}
.hero-tile-zoom::before{
  content:''; width:17px; height:17px;
  border:2.5px solid currentColor; border-radius:50%;
  box-shadow:6px 6px 0 -3.5px currentColor;
  translate:-2px -2px;
}
.hero-tile:hover .hero-tile-zoom,
.hero-tile:focus-visible .hero-tile-zoom,
.why-love-tile:hover .hero-tile-zoom,
.why-love-tile:focus-visible .hero-tile-zoom{ opacity:1; scale:1; }

/* ── Hero stats: Rating · Time Needed · Made This ─────────────────────── */
.hero-stats[hidden]{ display:none; }
.stat-star{ font-size:.7em; margin-left:2px; }
/* ⤷ beats stitchobsession.css .stat-number{font-size:clamp(1.7rem,3vw,2.4rem)} —
   a text value like "Intermediate" or "2-3 Hours" cannot ride the numeral
   scale without wrapping out of its column. */
.hero-stats .stat-number--text{
  font-size:clamp(1.05rem, 1.7vw, 1.35rem);
  line-height:1.15;
  /* --red is cleared for DISPLAY sizes only: 4.22:1 passes the 3:1 large-text
     bar at ≥24px, and stitchobsession.css says so in a comment right next to the
     rule. This clamp caps at 21.6px, under the threshold, so the same red
     inherited from .stat-number needs 4.5:1 and misses. Two of the four values
     in this row rendered at 38.4px and passed while these two failed. */
  color:var(--red-d);                                    /* 6.1:1 ✓ AA */
  text-shadow:1.5px 1.5px 0 var(--ink);
}
/* The star rides the same sub-24px text and inherited the same failing red. */
.hero-stats .stat-number--text .stat-star{ color:var(--red-d); }


/* ═══════════════════════════════════════════════════════════════════════════
   3 · AT A GLANCE (intro features)
   ═══════════════════════════════════════════════════════════════════════ */
.intro-section[hidden]{ display:none; }
.section-rubric{
  display:flex; align-items:center; justify-content:center; gap:var(--sp-3);
  text-align:center;
  color:var(--teal);                                     /* 5.01:1 ✓ AA */
  text-shadow:3px 3px 0 var(--paper-light);
  margin-bottom:var(--sp-6);
}
.feature-title{
  font-family:var(--display); font-size:1.15rem;
  letter-spacing:var(--track-tight); line-height:1.15;
  color:var(--ink);
  margin:0 0 var(--sp-2);
}
.feature-body{
  font-size:var(--fs-sm); line-height:1.62;
  color:var(--ink-2);                                    /* 7.44:1 ✓ AA */
  margin:0;
}


/* ═══════════════════════════════════════════════════════════════════════════
   4 · CONTENT CARDS + SIDEBAR
   ═══════════════════════════════════════════════════════════════════════ */
.content-main > .content-card:last-child{ margin-bottom:0; }
.content-card[hidden]{ display:none; }

/* Filigree colour follows the ROLE rule in stitchobsession.css §5.3 (red = the
   pattern card, ink = reading panels, teal = aside/utility panels, mustard =
   chrome plates). The renderer hands each content card one of the modifier
   classes — mustard, teal, mustard, ink down one article — so the roles are
   pinned here with a class more of specificity, which is what lets these
   rules beat the 0,1,0 modifiers regardless of load order. */
.content-card.sp-framed{ --fil-color:var(--ink); }
.side-promo.sp-framed{   --fil-color:var(--teal); }

/* ── The numbered marker — TWO treatments, site-wide ──────────────────────
   1. THE DISC: a mustard plate, ink numeral in the display face, ink keyline,
      hard shadow. Marks a SECTION: the at-a-glance feature cards (§12 of the
      system), each content-card heading, each common mistake, each related
      pattern. One plate, one colour — the red disc on the card headings and
      the square on the rec cards were the same job drawn three ways within
      200px of each other.
   2. THE BARE NUMERAL: display face, --mustard-d, no plate. Marks an ITEM in
      a list you work through — materials, abbreviations. Same size in both.
   Ink on mustard is 7.19:1. */
.content-title-mark{
  display:grid; place-items:center; flex:none;
  width:40px; height:40px;
  font-family:var(--display); font-size:1rem; line-height:1;
  color:var(--on-mustard); background:var(--mustard);    /* 7.19:1 ✓ AAA */
  border:var(--bw) solid var(--line); border-radius:50%;
  box-shadow:var(--pop-sm);
  rotate:-6deg;
}
.card-lede{
  font-family:var(--script); font-size:1.35rem; line-height:1.35;
  color:var(--teal-d);                                   /* 7.26:1 ✓ AAA */
  margin:calc(-1 * var(--sp-2)) 0 var(--sp-4);
}
.prose-block p{
  font-size:var(--fs-body); line-height:var(--lh-body);
  color:var(--ink-2);                                    /* 7.44:1 ✓ AA */
  margin:0 0 var(--sp-4);
  max-width:var(--measure);
}
.prose-block p:last-child{ margin-bottom:0; }
/* Drop cap on the opening paragraph — the poster's printed-page voice. */
.prose-block > p:first-of-type::first-letter{
  float:left;
  font-family:var(--display); font-size:3.1em; line-height:.82;
  color:var(--red-d);                                    /* 6.64:1 ✓ AA */
  margin:.06em .12em 0 0;
}
.answer-summary{
  padding:var(--sp-4);
  background:var(--cream-deep);
  border-left:var(--bw-slab) solid var(--mustard);
  border-radius:var(--r-xs);
  margin-bottom:var(--sp-4);
}
.answer-summary p{ margin-bottom:var(--sp-2); color:var(--on-cream); }
.answer-summary p:last-child{ margin-bottom:0; }

/* ── Why You'll Love This ─────────────────────────────────────────────── */
.why-lead{
  font-size:var(--fs-lg); line-height:var(--lh-loose);
  color:var(--ink-2);
  margin:0 0 var(--sp-5);
  max-width:var(--measure);
}
.why-point{
  position:relative;
  padding:0 0 var(--sp-4) var(--sp-6);
  margin-bottom:var(--sp-4);
  border-bottom:var(--bw-hair) dashed var(--line-soft);
}
.why-point:last-child{ border-bottom:0; padding-bottom:0; margin-bottom:0; }
.why-point::before{
  content:''; position:absolute; left:0; top:.35em;
  width:20px; height:20px;
  background-color:var(--mustard);
  -webkit-mask-image:var(--m-star12); mask-image:var(--m-star12);
  -webkit-mask-size:contain;          mask-size:contain;
  -webkit-mask-repeat:no-repeat;      mask-repeat:no-repeat;
}
.why-point-title{
  font-family:var(--display); font-size:1.08rem; line-height:1.2;
  letter-spacing:var(--track-tight);
  color:var(--teal-d);                                   /* 7.26:1 ✓ AAA */
  margin:0 0 var(--sp-2);
}
.why-point-body{
  font-size:var(--fs-body); line-height:var(--lh-body);
  color:var(--ink-2); margin:0;
}

/* The step-photo grid inside that card — two up by default. */
.why-love-images-grid{
  display:grid; gap:var(--sp-4);
  grid-template-columns:repeat(2, minmax(0,1fr));
  margin-top:var(--sp-6);
}
.why-love-images-grid.hidden{ display:none; }
.why-love-images-grid[data-count="1"]{ grid-template-columns:1fr; }
.why-love-tile img{ aspect-ratio:1; }
/* Count-aware, so an odd number of photos never leaves a hole. Three tiles
   in a two-up grid left a 333×340px empty cell beside the third (dahlia at
   1440); three go three across instead, and in the hero grid too, which had
   the same 2 + 1 shape. Any OTHER odd count (5, 7) lets its last tile span
   both columns as a wide crop. Scoped above the phone breakpoint: below it
   the ≤760px block sets one column and there is nothing to fill. */
@media (min-width:761px){
  .why-love-images-grid[data-count="3"],
  .hero-thumbnail-grid[data-count="3"]{ grid-template-columns:repeat(3, minmax(0,1fr)); }
  .why-love-images-grid:not([data-count="3"]) > .why-love-tile:last-child:nth-child(odd):not(:first-child){
    grid-column:1 / -1;
  }
  .why-love-images-grid:not([data-count="3"]) > .why-love-tile:last-child:nth-child(odd):not(:first-child) img{
    aspect-ratio:2 / 1;
  }
}

/* ── Switch Things Up ─────────────────────────────────────────────────── */
.tips-content-list{ list-style:none; padding:0; margin:0; display:grid; gap:var(--sp-3); }
.tips-content-list li{
  display:flex; gap:var(--sp-3); align-items:flex-start;
  padding:var(--sp-4);
  background:var(--cream-deep);
  border:var(--bw-hair) solid var(--line);
  border-radius:var(--r-sm);
  font-size:var(--fs-body); line-height:var(--lh-body);
  color:var(--on-cream);                                 /* 10.74:1 ✓ AAA */
}
.tips-content-list strong{ color:var(--red-d); }         /* 5.13:1 on cream ✓ */
.tip-mark{
  flex:none; width:22px; height:22px; margin-top:.2em;
  background-color:var(--teal);
  -webkit-mask-image:var(--m-sparkle); mask-image:var(--m-sparkle);
  -webkit-mask-size:contain;           mask-size:contain;
  -webkit-mask-repeat:no-repeat;       mask-repeat:no-repeat;
}

/* ── Common Mistakes & How to Fix Them ────────────────────────────────── */
.mistakes-card{ background:var(--paper); }
.mistakes-list{ list-style:none; padding:0; margin:0; display:grid; gap:var(--sp-4); }
.mistake-item{
  display:flex; gap:var(--sp-3); align-items:flex-start;
  padding:var(--sp-4);
  background:var(--paper-light);
  border:var(--bw) solid var(--line);
  border-left:var(--bw-slab) solid var(--red);
  border-radius:var(--r-sm);
  box-shadow:var(--pop-sm);
}
.mistake-mark{                                           /* treatment 1: the disc */
  display:grid; place-items:center; flex:none;
  width:30px; height:30px;
  font-family:var(--display); font-size:1rem; line-height:1;
  color:var(--on-mustard); background:var(--mustard);    /* 7.19:1 ✓ AAA */
  border:var(--bw-hair) solid var(--line); border-radius:50%;
}
.mistake-body{ display:flex; flex-direction:column; gap:6px; }
.mistake-what{
  font-family:var(--caps); font-weight:600; font-size:var(--fs-lg);
  text-transform:uppercase; letter-spacing:.02em;
  color:var(--red-deep);                                 /* 7.03:1 ✓ AAA */
  line-height:1.25;
}
.mistake-fix{
  font-size:var(--fs-body); line-height:var(--lh-body);
  color:var(--ink-2);                                    /* 7.44:1 ✓ AA */
}
.mistake-fix::before{
  content:'The fix — ';
  font-family:var(--caps); font-weight:600; font-size:.86em;
  text-transform:uppercase; letter-spacing:var(--track-caps);
  color:var(--teal-d);                                   /* 6.87:1 ✓ AAA */
}

/* ── Sidebar ──────────────────────────────────────────────────────────── */
/* .side-sticky was sticky but .content-side is the GRID ITEM, and the grid
   uses align-items:start — so the aside was 1,220px tall in a 5,181px row and
   the sticky child had nowhere to travel. The right column sat empty for
   3,961px, 76% of the article. Sticking the item itself is what makes the
   inner panel follow the reader down a long pattern. */
.content-side{ display:block; }
.side-sticky{ display:grid; gap:var(--sp-5); }
@media (min-width:1100px){
  .content-side{
    position:sticky;
    top:calc(var(--nav-h) + var(--sp-4) - 6px);
    align-self:start;
    max-height:calc(100dvh - var(--nav-h) - var(--sp-6));
    overflow-y:auto;
    overscroll-behavior:contain;
    /* A scroll container clips at its padding edge, and three things in this
       column hang past it: the promo's "no spam" seal (12px off the right),
       the sticker-rotated author card's corners (~4px) and every 5px hard
       shadow. The seal was visibly cut in half. Grow the box into the grid
       gap with negative margins and hand the room back as padding, so the
       content sits exactly where it did and the clip edge moves out. */
    padding:6px var(--sp-4) var(--sp-3) 6px;
    margin:-6px calc(-1 * var(--sp-4)) 0 -6px;   /* ≤ the 20px gutter that applies at exactly 1100px */
  }
  .side-sticky{ position:static; }
}
.author-portrait--mark{
  display:grid; place-items:center;
  font-size:2.1rem; line-height:1;
}
/* ⤷ This needs the extra .author-card specificity to beat stitchobsession.css
   .sp-script{color:var(--teal)}. article.css loads BEFORE stitchobsession.css and
   both rules were 0,1,0, so the plain .author-kicker colour below never
   rendered — the element is <p class="author-kicker sp-script"> and it came
   out teal at 4.29:1 on --cream-deep, failing AA at 22.4px/600. The old
   "5.13:1 on cream" annotation described a colour the page never painted. */
.author-kicker{
  font-size:1.15rem;
  margin:0 0 var(--sp-2);
}
.author-card .author-kicker{
  color:var(--teal-d);                    /* 6.0:1 on --cream-deep ✓ AA */
}
.author-name{ color:var(--ink); }
/* ⤷ beats stitchobsession.css .social-icon{color:var(--on-ink)} — that rule is
   written for the ink footer; cream-on-cream is invisible on this card. */
.author-card .social-icon{
  width:44px; height:44px;                 /* was 42 — under the tap minimum */
  color:var(--ink);                                      /* 10.74:1 ✓ AAA */
  border-color:var(--line);
  background:var(--paper-light);
}
.author-card .social-icon:hover{ background:var(--mustard); color:var(--on-mustard); }

/* In the 320px sidebar the portrait-beside-text row left the bio a 203px
   column — 30 characters a line, six lines of ragged script-width prose.
   Restack without touching the markup: the card becomes a grid, the
   unnamed wrapper div goes display:contents so its four children place
   themselves, and the bio + social row take the full card width under the
   portrait/name pair (303px — 45 characters a line). The ⤷ specificity
   beats stitchobsession.css .author-card{display:flex} (loaded after this). */
@media (min-width:1100px){
  .content-side .author-card{
    display:grid;
    grid-template-columns:auto minmax(0,1fr);
    column-gap:var(--sp-4); row-gap:0;
    align-items:start;
  }
  .content-side .author-card > .author-portrait{ grid-column:1; grid-row:1 / span 2; }
  .content-side .author-card > div{ display:contents; }
  .content-side .author-card .author-name{ grid-column:2; grid-row:1; align-self:end; margin:0; }
  .content-side .author-card .author-kicker{ grid-column:2; grid-row:2; margin:0; }
  .content-side .author-card .author-bio{ grid-column:1 / -1; grid-row:3; margin-top:var(--sp-3); }
  .content-side .author-card .social-links{ grid-column:1 / -1; grid-row:4; }
}

.side-block{
  padding:var(--sp-5);
  background:var(--paper-light);
  border:var(--bw) solid var(--line);
  border-radius:var(--r);
  box-shadow:var(--pop-sm);
}
.pattern-cards-container{ display:grid; gap:var(--sp-3); }
.rec-card-mark{                                          /* treatment 1: the disc */
  display:grid; place-items:center; flex:none;
  width:44px; height:44px;
  font-family:var(--display); font-size:1rem;
  color:var(--on-mustard); background:var(--mustard);    /* 7.19:1 ✓ AAA */
  border:var(--bw-hair) solid var(--line); border-radius:50%;
}
.rec-card-title{
  font-size:var(--fs-sm); font-weight:var(--fw-bold); line-height:1.35;
  color:var(--ink);
}
.rec-all{ margin-top:var(--sp-3); }

.side-promo{ position:relative; text-align:center; }
/* The seal is a corner sticker on a panel whose copy is CENTRED, so it only
   works once the panel is wide enough to have empty corners. In the stacked
   mobile layout the panel is 339px and the 99px disc sat straight across the
   script line — "Straight to your door —" was cut off mid-phrase — while its
   29px overhang also clipped the bottom-right of the recommendations card
   above it. Same call, and the same reason, as .patterns-hero-stamp in
   stitchobsession.css: the ornament goes where the copy already fills the width.
   1100px is where the sidebar becomes a real column (see .content-side). */
.side-promo-seal{ display:none; }
@media (min-width:1100px){
  .side-promo-seal{
    display:inline-flex;
    /* 8px past the panel, not 12: with the ring (7px) and shadow (3px) that is
       the 18px of room the sticky column's clip edge was given. */
    position:absolute; top:calc(-1 * var(--sp-5)); right:calc(-1 * var(--sp-2));
  }
}
.side-promo-kicker{
  font-family:var(--script); font-size:1.3rem;
  color:var(--teal-d); margin:0 0 var(--sp-1);
}
.side-promo-title{
  font-family:var(--display); font-size:1.35rem;
  text-transform:uppercase; letter-spacing:var(--track-tight);
  color:var(--red-d);                                    /* 5.13:1 on cream ✓ */
  margin:0 0 var(--sp-3);
}
.side-promo-body{
  font-size:var(--fs-sm); line-height:1.6;
  color:var(--on-cream); margin:0 0 var(--sp-4);
}


/* ═══════════════════════════════════════════════════════════════════════════
   5 · THE PATTERN CARD
   ═══════════════════════════════════════════════════════════════════════ */
.pattern-eyebrow{ margin-bottom:var(--sp-4); }
.pattern-content-wrapper{
  display:grid; gap:var(--sp-6);
  grid-template-columns:minmax(0,1fr);
  align-items:start;
}
.pattern-main{ min-width:0; }
.pattern-card{
  position:relative;
  background:var(--paper);
  border:var(--bw-thick) solid var(--line);
  border-radius:var(--r-lg);
  box-shadow:var(--pop-xl);
  padding:var(--sp-7) var(--sp-6);
}
.pattern-title{
  font-size:clamp(1.6rem, 3.4vw, 2.5rem);
  line-height:var(--lh-poster);
  text-transform:uppercase;
  color:var(--red);                        /* ≥24px display → 4.22:1 ≥ 3:1 ✓ */
  text-shadow:var(--hard-x) var(--hard-y) 0 var(--ink);
  margin:0 0 var(--sp-4);
}
.pattern-description{
  font-size:var(--fs-lg); line-height:var(--lh-loose);
  color:var(--ink-2);                                    /* 6.71:1 ✓ AA */
  max-width:var(--measure);
  margin:0 0 var(--sp-5);
}
/* ⤷ beats stitchobsession.css .skill-level — the system styles that class as a
   single chip (nowrap + ellipsis). Here it is the CONTAINER of the chips. */
.pattern-card .skill-level{
  display:flex; flex-wrap:wrap; gap:var(--sp-2);
  max-width:none; padding:0; margin:0 0 var(--sp-6);
  background:none; border:0; border-radius:0;
  white-space:normal; overflow:visible;
}
.skill-item{
  display:inline-flex; align-items:center; gap:6px;
  padding:6px var(--sp-3);
  font-family:var(--caps); font-weight:600; font-size:var(--fs-xs);
  text-transform:uppercase; letter-spacing:var(--track-caps);
  color:var(--on-cream); background:var(--cream-deep);   /* 10.74:1 ✓ AAA */
  border:var(--bw-hair) solid var(--line);
  border-radius:var(--r-pill);
  box-shadow:var(--pop-sm);
}
.skill-item:first-child{ background:var(--mustard); color:var(--on-mustard); }
.pattern-placeholder{ text-align:center; font-size:4rem; padding:var(--sp-5); }
.pattern-placeholder.hidden{ display:none; }

/* ── A part of the pattern: "— Main Pattern :" ────────────────────────── */
.part-section{
  position:relative;
  margin:var(--sp-7) 0 0;
  padding-top:var(--sp-5);
  border-top:var(--bw-hair) solid var(--line-soft);
}
.pattern-card > div:first-child .part-section:first-child{ margin-top:0; border-top:0; padding-top:0; }
.part-title{
  display:flex; align-items:baseline; flex-wrap:wrap; gap:var(--sp-2);
  font-family:var(--display);
  font-size:clamp(1.25rem, 2.4vw, 1.7rem);
  line-height:1.15;
  text-transform:uppercase; letter-spacing:var(--track-tight);
  color:var(--teal);                       /* ≥24px display → 5.01:1 ✓ */
  margin:0 0 var(--sp-4);
}
.part-qty{
  font-family:var(--caps); font-size:var(--fs-sm); font-weight:600;
  letter-spacing:var(--track-caps);
  color:var(--on-red); background:var(--red);            /* 4.96:1 ✓ AA */
  padding:3px var(--sp-2);
  border:var(--bw-hair) solid var(--line);
  border-radius:var(--r-pill);
}
.part-note{
  font-family:var(--script); font-size:1.2rem;
  color:var(--teal-d); margin:calc(-1 * var(--sp-2)) 0 var(--sp-4);
}
.part-section.part-complete .part-title{ color:var(--teal-d); }
.part-section.part-complete .part-title::after{
  content:''; width:22px; height:22px;
  background-color:var(--teal);
  -webkit-mask-image:var(--m-star12); mask-image:var(--m-star12);
  -webkit-mask-size:contain;          mask-size:contain;
  -webkit-mask-repeat:no-repeat;      mask-repeat:no-repeat;
}

/* ── Materials: numbered 01 / 02 / 03, tickable ───────────────────────── */
.material-group-name{
  font-family:var(--caps); font-weight:600; font-size:var(--fs-sm);
  text-transform:uppercase; letter-spacing:var(--track-wide);
  color:var(--red-d);                                    /* 5.98:1 ✓ AA */
  margin:var(--sp-4) 0 var(--sp-2);
}
.material-list{ list-style:none; padding:0; margin:0 0 var(--sp-4); display:grid; gap:var(--sp-2); }
.material-item{
  position:relative;
  display:flex; align-items:center; gap:var(--sp-4);
  min-height:56px;
  padding:var(--sp-3) var(--sp-4);
  background:var(--paper-light);
  border:var(--bw-hair) solid var(--line);
  border-radius:var(--r-sm);
  cursor:pointer;
  transition:background-color var(--t-fast) var(--ease),
             translate var(--t-fast) var(--ease),
             box-shadow var(--t-fast) var(--ease);
}
.material-item:hover{ translate:-2px -2px; box-shadow:var(--pop-sm); }
.material-item:focus-visible{ outline:3px solid var(--focus); outline-offset:2px; }
.material-number{                                        /* treatment 2: the bare numeral */
  flex:none;
  font-family:var(--display); font-size:1rem; line-height:1;
  color:var(--mustard-d);                                /* 6.87:1 on card stock ✓ AA */
  min-width:2.4ch;
}
.material-text{
  flex:1;
  font-size:var(--fs-body); line-height:1.5;
  color:var(--ink);                                      /* 13.90:1 ✓ AAA */
}
.material-tick{
  flex:none; display:grid; place-items:center;
  width:26px; height:26px;
  background:var(--paper);
  border:var(--bw) solid var(--line);
  border-radius:var(--r-xs);
}
.material-item.completed{ background:var(--cream-deep); }
.material-item.completed .material-text{ text-decoration:line-through; color:var(--ink-3); }
.material-item.completed .material-tick{ background:var(--teal); border-color:var(--line); }
.material-item.completed .material-tick::after{
  content:''; width:12px; height:7px;
  border-left:3px solid var(--on-teal); border-bottom:3px solid var(--on-teal);
  rotate:-45deg; translate:0 -2px;
}


/* ═══════════════════════════════════════════════════════════════════════════
   6 · PROGRESS TRACKER + ROUNDS
   ═══════════════════════════════════════════════════════════════════════ */
.progress-tracker{
  position:relative;
  padding:var(--sp-4) var(--sp-5);
  margin:0 0 var(--sp-5);
  background:var(--ink);
  color:var(--on-ink);                                   /* 12.53:1 ✓ AAA */
  border:var(--bw) solid var(--line);
  border-radius:var(--r-sm);
  box-shadow:var(--pop);
}
.progress-tracker-head{
  display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between;
  gap:var(--sp-3); margin-bottom:var(--sp-3);
}
.progress-tracker-title{
  display:flex; align-items:center; gap:var(--sp-2);
  font-family:var(--caps); font-weight:600; font-size:var(--fs-sm);
  text-transform:uppercase; letter-spacing:var(--track-wide);
  color:var(--mustard);                                  /* 7.19:1 on ink ✓ */
  margin:0;
}
.progress-tracker-mark{
  width:16px; height:16px; flex:none;
  background-color:var(--mustard);
  -webkit-mask-image:var(--m-star12); mask-image:var(--m-star12);
  -webkit-mask-size:contain;          mask-size:contain;
  -webkit-mask-repeat:no-repeat;      mask-repeat:no-repeat;
}
.progress-tracker-count{
  font-family:var(--display); font-size:1.25rem; line-height:1;
  letter-spacing:var(--track-tight);
  color:var(--on-ink); margin:0;
}
.progress-bar-track{
  height:18px; overflow:hidden;
  background:rgba(244,228,193,.16);
  border:var(--bw-hair) solid var(--on-ink-mut);
  border-radius:var(--r-pill);
}
.progress-bar-fill{
  height:100%; width:0;
  background:var(--mustard);
  background-image:repeating-linear-gradient(
    135deg, rgba(43,33,24,.22) 0 6px, transparent 6px 14px);
  border-radius:var(--r-pill);
  transition:width var(--t-slow) var(--ease-out);
}
.progress-tracker-hint{
  margin:var(--sp-2) 0 0;
  font-size:var(--fs-xs); line-height:1.5;
  color:var(--on-ink-mut);                               /* 9.17:1 ✓ AAA */
}

.step-by-step{ display:grid; gap:var(--sp-3); }

/* ── One round ────────────────────────────────────────────────────────── */
.row-instruction{
  position:relative;
  padding:var(--sp-4) var(--sp-6) var(--sp-4) var(--sp-5);
  background:var(--paper-light);
  border:var(--bw-hair) solid var(--line);
  border-left:var(--bw-slab) solid var(--teal);
  border-radius:var(--r-sm);
  transition:background-color var(--t-fast) var(--ease),
             translate var(--t-fast) var(--ease),
             box-shadow var(--t-fast) var(--ease),
             border-left-color var(--t-fast) var(--ease);
}
.row-instruction[data-round]{ cursor:pointer; }
.row-instruction[data-round]:hover{ translate:-2px -2px; box-shadow:var(--pop-sm); }
.row-instruction[data-round]:focus-visible{ outline:3px solid var(--focus); outline-offset:3px; }

.row-label{
  display:inline-flex; align-items:baseline; gap:2px;
  font-family:var(--display); font-size:1.02rem; line-height:1.15;
  letter-spacing:var(--track-tight);
  text-transform:uppercase;
  color:var(--red-d);                                    /* 6.64:1 ✓ AA */
  margin:0 0 var(--sp-2);
}
.row-label-colon{ color:var(--mustard-d); }              /* 6.87:1 on card stock ✓ AA */
.row-text{
  font-size:var(--fs-body); line-height:var(--lh-body);
  color:var(--ink);                                      /* 13.90:1 ✓ AAA */
  margin:0;
}
.row-subhead{
  font-family:var(--caps); font-weight:600;
  font-size:var(--fs-lg);
  text-transform:uppercase; letter-spacing:var(--track-caps);
  color:var(--teal-d);                                   /* 7.26:1 ✓ AAA */
  margin:var(--sp-5) 0 var(--sp-2);
  padding-bottom:var(--sp-2);
  border-bottom:var(--bw-hair) dashed var(--line-soft);
}

/* THE STITCH COUNT — pulled out of the sentence at runtime and set in red
   slab. Only ever a count that was already in the data. */
.row-count{
  display:inline-block;
  margin-left:.35em;
  padding:2px 10px;
  font-family:var(--display); font-size:.95em; line-height:1.25;
  letter-spacing:var(--track-tight);
  white-space:nowrap;
  color:var(--on-red); background:var(--red);            /* 4.96:1 ✓ AA */
  border:var(--bw-hair) solid var(--line);
  border-radius:var(--r-pill);
  box-shadow:2px 2px 0 var(--ink);
  translate:0 -1px;
}

/* The tick that appears when a round is marked done. */
.row-tick{
  position:absolute; top:var(--sp-4); right:var(--sp-3);
  display:grid; place-items:center;
  width:28px; height:28px;
  background:var(--paper);
  border:var(--bw) solid var(--line);
  border-radius:50%;
  transition:background-color var(--t-fast) var(--ease), scale var(--t) var(--ease);
}
.row-instruction.completed{
  background:var(--cream-deep);
  border-left-color:var(--teal-d);
}
.row-instruction.completed .row-text{ color:var(--ink-2); }
.row-instruction.completed .row-label{ color:var(--teal-d); }
.row-instruction.completed .row-tick{ background:var(--teal); scale:1.06; }
.row-instruction.completed .row-tick::after{
  content:''; width:13px; height:7px;
  border-left:3px solid var(--on-teal); border-bottom:3px solid var(--on-teal);
  rotate:-45deg; translate:0 -2px;
}

/* Row flavours that are NOT steps. */
.row-instruction.kind-info{
  background:var(--cream-deep);
  border-left-color:var(--mustard);
  padding-right:var(--sp-5);
}
.row-instruction.kind-info .row-label{ color:var(--teal-d); }
.row-instruction.kind-info .row-text{ color:var(--on-cream); }
.row-instruction.kind-assembly{ border-left-color:var(--red); }
.row-instruction.kind-note,
.row-instruction.kind-tester{
  background:var(--paper);
  border-left-color:var(--mustard);
  padding-right:var(--sp-5);
}
.row-instruction.kind-note .row-label,
.row-instruction.kind-tester .row-label{
  display:flex; align-items:center; gap:var(--sp-2);
  /* --mustard-d was retuned for exactly this block: on --paper, under the
     grain, the old value measured 4.28:1. Now 6.19 flat, 5.74 composited. */
  color:var(--mustard-d);                                /* 6.19:1 on paper ✓ AA */
}
/* The renderer puts a 💡 in this span — a colour emoji, and Segoe paints it
   in a yellow this palette does not own. Same treatment as §5.5b of the
   system: zero the glyph, keep the box, paint the sparkle mask from the
   label's own colour. The character stays in the markup. */
.note-icon{
  display:inline-block; flex:none;
  width:18px; height:18px;
  font-size:0;
  background-color:currentColor;
  -webkit-mask-image:var(--m-sparkle); mask-image:var(--m-sparkle);
  -webkit-mask-size:contain;           mask-size:contain;
  -webkit-mask-repeat:no-repeat;       mask-repeat:no-repeat;
  -webkit-mask-position:center;        mask-position:center;
}

.conclusion{
  margin-top:var(--sp-6);
  padding:var(--sp-5);
  background:var(--cream-deep);
  border:var(--bw) solid var(--line);
  border-radius:var(--r-sm);
  box-shadow:var(--pop-sm);
}
.conclusion p{
  font-family:var(--script); font-size:1.4rem; line-height:1.45;
  color:var(--teal-d);                                   /* 6.22:1 on cream ✓ */
  margin:0 0 var(--sp-2);
}
.conclusion p:last-child{ margin-bottom:0; }
.conclusion:empty{ display:none; }


/* ═══════════════════════════════════════════════════════════════════════════
   7 · ABBREVIATIONS — sticky aside ≥1100px, drawer below
   ═══════════════════════════════════════════════════════════════════════ */
.pattern-sidebar{ min-width:0; }
.sidebar-close-wrap{ display:none; }
.abbreviations-card{
  background:var(--paper-light);
  border:var(--bw) solid var(--line);
  border-radius:var(--r);
  box-shadow:var(--pop);
  padding:var(--sp-5);
}
.abbreviations-kicker{
  font-family:var(--script); font-size:1.2rem;
  color:var(--red-d); margin:0 0 var(--sp-1);            /* 6.64:1 ✓ AA */
}
.abbreviations-title{
  font-family:var(--display); font-size:1.35rem;
  text-transform:uppercase; letter-spacing:var(--track-tight);
  color:var(--ink);
  text-shadow:2px 2px 0 var(--mustard);
  margin:0 0 var(--sp-2);
}
.abbreviations-sub{
  font-size:var(--fs-xs); line-height:1.5;
  color:var(--ink-2); margin:0 0 var(--sp-4);
  padding-bottom:var(--sp-3);
  border-bottom:var(--bw-hair) dashed var(--line-soft);
}
.abbrev-list{ list-style:none; padding:0; margin:0; display:grid; gap:var(--sp-3); }
.abbrev-item{ display:flex; gap:var(--sp-3); align-items:baseline; }
.abbrev-number{                                          /* treatment 2: the bare numeral */
  flex:none;
  font-family:var(--display); font-size:1rem; line-height:1.3;
  color:var(--mustard-d);                                /* 6.87:1 on card stock ✓ AA */
  min-width:2.4ch;
}
.abbrev-text{ display:flex; flex-direction:column; gap:2px; }
.abbrev-key{
  font-family:var(--caps); font-weight:600; font-size:var(--fs-sm);
  text-transform:uppercase; letter-spacing:var(--track-caps);
  color:var(--teal-d);                                   /* 7.26:1 ✓ AAA */
}
.abbrev-meaning{
  font-size:var(--fs-sm); line-height:1.5;
  color:var(--ink-2);                                    /* 7.44:1 ✓ AA */
}

/* The floating drawer toggle (mobile / tablet only). */
.mobile-abbrev-toggle{
  position:fixed; z-index:70; right:var(--sp-4);
  bottom:calc(var(--sp-4) + var(--sp-banner-h, 0px) + env(safe-area-inset-bottom));
  display:none; align-items:center; gap:var(--sp-2);
  min-height:52px; padding:12px var(--sp-4);
  font-family:var(--caps); font-weight:600; font-size:var(--fs-sm);
  text-transform:uppercase; letter-spacing:var(--track-caps);
  color:var(--on-red); background:var(--red);            /* 4.96:1 ✓ AA */
  border:var(--bw) solid var(--line);
  border-radius:var(--r-pill);
  box-shadow:var(--pop);
  cursor:pointer;
  /* `visibility` matters as much as `opacity` here. With opacity alone the
     button was invisible to the mouse (pointer-events:none) but still a live
     tab stop — stop 41 of 76 on a 390px article, reached in a jump from
     page-y 14370 back to y 798, and pressing Enter on it genuinely opened the
     abbreviations drawer from a trigger the user could not see. */
  opacity:0; visibility:hidden; translate:0 20px; pointer-events:none;
  transition:opacity var(--t) var(--ease), translate var(--t) var(--ease),
             visibility var(--t) var(--ease),
             box-shadow var(--t-fast) var(--ease);
}
.mobile-abbrev-toggle.visible{
  opacity:1; visibility:visible; translate:0 0; pointer-events:auto;
}
.mobile-abbrev-toggle:active{ translate:0 var(--press); box-shadow:var(--pop-in); }
.mobile-abbrev-toggle:focus-visible{ outline:3px solid var(--ink); outline-offset:3px; }

/* ⤷ beats stitchobsession.css .mobile-overlay{opacity:0;visibility:hidden} —
   the design system reveals this overlay on `.active` (its mobile-nav names
   the class that way). The drawer here uses `.open`, so the visible state has
   to be spelled out again, VISIBILITY INCLUDED: opacity alone leaves the
   overlay hidden and the drawer un-dimmed. The system's z-index (120) is
   deliberately left in place — see .pattern-sidebar below, which sits above
   it. */
.mobile-overlay{
  background:rgba(43,33,24,.62);
  transition:opacity var(--t) var(--ease), visibility var(--t) var(--ease);
}
.mobile-overlay.open{ opacity:1; visibility:visible; }


/* ═══════════════════════════════════════════════════════════════════════════
   8 · Q&A ACCORDION
   ═══════════════════════════════════════════════════════════════════════ */
/* .qa-section's padding and .qa-header's centring are declared in
   stitchobsession.css §17 with identical values; the copies that sat here could
   never win the tie (this file loads first) and were pure duplicate bytes. */
.qa-section[hidden]{ display:none; }
.qa-badge{
  display:inline-block; margin-bottom:var(--sp-3);
  padding:6px var(--sp-4);
  font-family:var(--caps); font-weight:600; font-size:var(--fs-xs);
  text-transform:uppercase; letter-spacing:var(--track-wide);
  color:var(--on-mustard); background:var(--mustard);    /* 7.19:1 ✓ AAA */
  border:var(--bw) solid var(--line);
  border-radius:var(--r-pill);
  box-shadow:var(--pop-sm);
  rotate:-2deg;
}
.qa-title{ margin:0 0 var(--sp-2); }
.qa-sub{ margin:0; }

/* The system draws the FAQ card on `.qa-list details`. This accordion is
   built from divs + buttons (so one item can be forced open at a time and the
   state can be announced), so the card surface has to be drawn here or the
   items float on the bare page with only a shadow behind them. */
.qa-item{
  overflow:hidden;
  background:var(--paper-light);
  border:var(--bw) solid var(--line);
  border-radius:var(--r-sm);
  box-shadow:var(--pop-sm);
  transition:box-shadow var(--t) var(--ease);
}
.qa-question-wrap{ margin:0; font-size:inherit; }
/* The question is a real <button>: keyboard operable with no JS, and the
   system's summary styling still applies to it. */
.qa-list .qa-question{
  width:100%;
  margin:0;
  background:transparent;
  border:0;
  text-align:left;
  font-family:var(--caps);
}
.qa-question-text{ flex:1; }
/* ⤷ beats stitchobsession.css .qa-icon{content:'+'} — replaced content on a
   normal element is not reliable across browsers, so the glyph is drawn by a
   pseudo-element instead and the replacement is switched back off. */
.qa-list .qa-item .qa-icon{ content:normal; }
.qa-list .qa-item .qa-icon::after{
  content:'+';
  font-family:var(--display); font-size:1.3rem; line-height:1;
}
.qa-item .qa-icon{ transition:rotate var(--t) var(--ease), background-color var(--t) var(--ease); }
.qa-item.active .qa-icon{ rotate:45deg; background:var(--red); color:var(--on-red); }
.qa-list .qa-item .qa-answer{ display:none; }
.qa-list .qa-item.active .qa-answer{ display:block; }
.qa-answer p{ margin:0; font-size:var(--fs-body); line-height:var(--lh-loose); }
.qa-item.active{ box-shadow:var(--pop); }


/* ═══════════════════════════════════════════════════════════════════════════
   9 · NEWSLETTER ADDITIONS
   ═══════════════════════════════════════════════════════════════════════ */
.newsletter-left{ display:flex; flex-direction:column; justify-content:center; }
/* .newsletter-kicker / .newsletter-seal / .req / .nl-live / the submit width
   all live in stitchobsession.css §10 with identical values. They were declared
   here too, which is worse than redundant: article.css is loaded BEFORE
   stitchobsession.css, so the later file wins every equal-specificity tie and a
   future edit to this copy would have changed nothing while looking like it
   should. Deleted — one owner per rule. */


/* ═══════════════════════════════════════════════════════════════════════════
   10 · SOURCE ATTRIBUTION
   ═══════════════════════════════════════════════════════════════════════ */
.source-attribution.hidden{ display:none; }
.source-attribution{
  margin-top:var(--sp-6);
  padding:var(--sp-4);
  background:var(--cream-deep);
  border:var(--bw-hair) dashed var(--line);
  border-radius:var(--r-sm);
  font-size:var(--fs-xs); line-height:1.6;
  color:var(--ink-2);
}
.credit-designer{ font-weight:var(--fw-bold); color:var(--ink); margin:0 0 4px; }
.credit-license,
.credit-source{ margin:0 0 4px; }


/* ═══════════════════════════════════════════════════════════════════════════
   11 · MODALS — image · newsletter result · completion
   ═══════════════════════════════════════════════════════════════════════ */
.sp-modal{
  position:fixed; inset:0; z-index:200;
  display:grid; place-items:center;
  padding:var(--gutter);
  background:rgba(43,33,24,.72);
  opacity:0;
  transition:opacity var(--t) var(--ease);
}
.sp-modal.open{ opacity:1; }
.sp-modal[hidden]{ display:none; }
.sp-modal-card{
  position:relative;
  width:min(100%, 560px);
  /* dvh, like the drawers: 100vh includes the strip under a phone's browser
     chrome, so the card's bottom could sit below the visible edge. */
  max-height:calc(100vh - var(--sp-7));
  max-height:calc(100dvh - var(--sp-7));
  overflow:auto;
  padding:var(--sp-6);
  background:var(--paper-light);
  border:var(--bw-thick) solid var(--line);
  border-radius:var(--r-lg);
  box-shadow:var(--pop-xl);
  scale:.96;
  transition:scale var(--t) var(--ease-out);
}
.sp-modal.open .sp-modal-card{ scale:1; }
.sp-modal-close{
  position:absolute; z-index:6; top:var(--sp-3); right:var(--sp-3);
  display:grid; place-items:center;
  width:44px; height:44px;
  font-family:var(--display); font-size:1.5rem; line-height:1;
  color:var(--on-red); background:var(--red);            /* 4.96:1 ✓ AA */
  border:var(--bw) solid var(--line);
  border-radius:50%;
  box-shadow:var(--pop-sm);
  cursor:pointer;
}
.sp-modal-close:hover{ background:var(--red-d); color:var(--on-red); }
.sp-modal-close:focus-visible{ outline:3px solid var(--mustard); outline-offset:3px; }

/* Image lightbox */
.image-modal-card{ width:min(100%, 900px); padding:var(--sp-5); text-align:center; }
.image-modal-card img{
  display:block; width:100%; height:auto;
  max-height:calc(100vh - 220px);
  max-height:calc(100dvh - 220px); object-fit:contain;
  background:var(--cream-deep);
  border:var(--bw) solid var(--line);
  border-radius:var(--r-sm);
}
.image-modal-caption{
  margin:var(--sp-3) 0 0;
  font-family:var(--script); font-size:1.25rem;
  color:var(--teal-d);                                   /* 7.26:1 ✓ AAA */
}

/* Newsletter result popup */
.nl-popup-card{ width:min(100%, 480px); padding:0; overflow:hidden; text-align:center; }
.nl-popup-head{
  padding:var(--sp-6) var(--sp-5) var(--sp-5);
  background:var(--teal); color:var(--on-teal);          /* 5.90:1 ✓ AA */
  border-bottom:var(--bw-thick) solid var(--line);
}
.nl-popup.is-error .nl-popup-head{ background:var(--red); color:var(--on-red); }
.nl-popup-icon{
  display:grid; place-items:center;
  width:64px; height:64px; margin:0 auto var(--sp-3);
  font-size:2rem; line-height:1;
  background:var(--paper-light); color:var(--ink);
  border:var(--bw) solid var(--line); border-radius:50%;
  box-shadow:var(--pop-sm);
  rotate:-6deg;
}
.nl-popup-title{
  font-family:var(--display); font-size:1.5rem;
  text-transform:uppercase; letter-spacing:var(--track-tight);
  color:inherit; margin:0;
  text-shadow:2px 2px 0 var(--ink);
}
.nl-popup-body{ padding:var(--sp-5); }
.nl-popup-body p{
  font-size:var(--fs-body); line-height:var(--lh-body);
  color:var(--ink-2); margin:0 0 var(--sp-5);
}
.nl-popup-actions{ display:flex; flex-wrap:wrap; gap:var(--sp-3); justify-content:center; }

/* Completion modal */
.completion-modal{ text-align:center; }
.completion-modal-emoji{ font-size:3rem; line-height:1; margin-bottom:var(--sp-3); }
.completion-modal-title{
  font-family:var(--display); font-size:1.6rem;
  text-transform:uppercase; letter-spacing:var(--track-tight);
  color:var(--red);                                      /* ≥24px ✓ */
  text-shadow:3px 3px 0 var(--ink);
  margin:0 0 var(--sp-3);
}
.completion-modal-sub{
  font-size:var(--fs-body); line-height:var(--lh-body);
  color:var(--ink-2); margin:0 0 var(--sp-5);
}
.completion-modal-form{ gap:var(--sp-3); }
.completion-modal-form input{
  width:100%; min-height:52px; padding:12px var(--sp-4);
  font-family:var(--font); font-size:var(--fs-body); color:var(--ink);
  background:var(--paper);
  border:var(--bw) solid var(--line); border-radius:var(--r-sm);
}
.completion-modal-skip{ margin:var(--sp-4) 0 0; }
.linkish{
  background:none; border:0; padding:6px;
  font-family:var(--caps); font-size:var(--fs-sm);
  text-transform:uppercase; letter-spacing:var(--track-caps);
  color:var(--ink-2);                                    /* 7.44:1 ✓ AA */
  text-decoration:underline; text-underline-offset:3px;
  cursor:pointer;
}
.linkish:hover{ color:var(--red-d); }
.linkish:focus-visible{ outline:3px solid var(--focus); outline-offset:2px; }


/* ═══════════════════════════════════════════════════════════════════════════
   12 · FLOATING PROGRESS + COMPLETION BADGE
   ═══════════════════════════════════════════════════════════════════════ */
/* --sp-banner-h is published by site-extras.js while the cookie banner is on
   screen and removed when it is dismissed, so this furniture stands on top of
   the banner instead of underneath it. Before this, a first-time visitor on a
   390px phone had the progress bar and the Abbreviations button 100% covered.
   The env() term keeps them off the iPhone home-indicator strip. */
.global-progress{
  position:fixed; z-index:60; left:var(--sp-4);
  bottom:calc(var(--sp-4) + var(--sp-banner-h, 0px) + env(safe-area-inset-bottom));
  width:min(280px, calc(100vw - 2 * var(--sp-4)));
  padding:var(--sp-3) var(--sp-4);
  background:var(--ink); color:var(--on-ink);            /* 12.53:1 ✓ AAA */
  border:var(--bw) solid var(--line);
  border-radius:var(--r-sm);
  box-shadow:var(--pop);
  /* Same reason as .mobile-abbrev-toggle: opacity:0 alone leaves a live
     progress dialog in the accessibility tree announcing "0% complete" on
     every article before the reader has scrolled anywhere. */
  opacity:0; visibility:hidden; translate:0 20px; pointer-events:none;
  transition:opacity var(--t) var(--ease), translate var(--t) var(--ease),
             visibility var(--t) var(--ease);
}
.global-progress.visible{ opacity:1; visibility:visible; translate:0 0; }
.global-progress-text{
  display:block; margin-bottom:var(--sp-2);
  font-family:var(--caps); font-weight:600; font-size:var(--fs-xs);
  text-transform:uppercase; letter-spacing:var(--track-caps);
  color:var(--mustard);                                  /* 7.19:1 on ink ✓ */
}
.global-progress-bar-track{
  height:10px; overflow:hidden;
  background:rgba(244,228,193,.18);
  border-radius:var(--r-pill);
}
.global-progress-bar-fill{
  height:100%; width:0;
  background:var(--mustard);
  border-radius:var(--r-pill);
  transition:width var(--t-slow) var(--ease-out);
}

.complete-badge{
  position:fixed; z-index:190; left:50%; top:22%;
  translate:-50% -20px;
  display:grid; gap:4px; justify-items:center;
  padding:var(--sp-5) var(--sp-7);
  text-align:center;
  background:var(--red); color:var(--on-red);            /* 4.96:1 ✓ AA */
  border:var(--bw-thick) solid var(--line);
  border-radius:var(--r-lg);
  box-shadow:var(--pop-xl);
  rotate:-3deg;
  /* Hidden for real: this is a live region, and at opacity:0 it still sat in
     the accessibility tree on every article announcing a completion that had
     not happened. */
  opacity:0; visibility:hidden; pointer-events:none;
  transition:opacity var(--t) var(--ease), translate var(--t) var(--ease),
             visibility var(--t) var(--ease);
}
.complete-badge.show{ opacity:1; visibility:visible; translate:-50% 0; }
.complete-badge-big{
  font-family:var(--display); font-size:1.7rem;
  text-transform:uppercase; letter-spacing:var(--track-tight);
  text-shadow:3px 3px 0 var(--ink);
}
.complete-badge-sub{ font-family:var(--script); font-size:1.3rem; }


/* ═══════════════════════════════════════════════════════════════════════════
   13 · FOOTER ADDITIONS
   ═══════════════════════════════════════════════════════════════════════ */
/* .footer-content--3 / .footer-note / .footer-legal are declared in
   stitchobsession.css §17 with identical values, and that file loads after this
   one, so these copies could never have won. Deleted for the same reason as
   the newsletter block above: one owner per rule. */


/* ═══════════════════════════════════════════════════════════════════════════
   14 · RESPONSIVE — the system's own breakpoints, 1100px and 760px
   ═══════════════════════════════════════════════════════════════════════ */
@media (min-width:1100px){
  .pattern-content-wrapper{ grid-template-columns:minmax(0,1fr) 320px; }
  .pattern-sidebar{ position:sticky; top:calc(var(--nav-h) + var(--sp-4)); }
  /* A sticky aside taller than the viewport pins its tail below the fold. At
     1440×900 the 16-term list on spring-wild-flower measured 1,380px, so terms
     09–16 could not be reached until the pattern itself ran out — which is the
     one moment nobody needs them. Cap the card at the viewport and scroll the
     LIST, not the card: the kicker, title and note stay pinned, and the
     background-attachment:local shadows only paint while there is more to
     see, so a short list looks exactly as it did. (.content-side got the same
     cap in the audit; this aside was missed.) */
  .abbreviations-card{
    display:flex; flex-direction:column;
    max-height:calc(100dvh - var(--nav-h) - var(--sp-4) - var(--sp-5));
  }
  .abbreviations-card > :not(.abbrev-list){ flex:none; }
  .abbrev-list{
    flex:1 1 auto; min-height:0;
    overflow-y:auto;
    scrollbar-width:thin;
    scrollbar-color:var(--line-soft) transparent;
    padding-right:var(--sp-2);
    background:
      linear-gradient(var(--paper-light) 30%, rgba(250,240,218,0)) center top / 100% 36px,
      linear-gradient(rgba(250,240,218,0), var(--paper-light) 70%) center bottom / 100% 36px,
      radial-gradient(farthest-side at 50% 0, rgba(43,33,24,.22), rgba(43,33,24,0)) center top / 100% 12px,
      radial-gradient(farthest-side at 50% 100%, rgba(43,33,24,.22), rgba(43,33,24,0)) center bottom / 100% 12px;
    background-repeat:no-repeat;
    background-attachment:local, local, scroll, scroll;
  }
}

@media (max-width:1099.98px){
  /* The abbreviations aside becomes a drawer. z-index 128 puts it above the
     design system's overlay (120) and below the mobile nav panel (130). */
  .pattern-sidebar{
    position:fixed; z-index:128; top:0; right:0; bottom:0;
    width:min(360px, 88vw);
    overflow-y:auto;
    padding:var(--sp-5) var(--sp-4);
    background:var(--paper-light);
    border-left:var(--bw-thick) solid var(--line);
    box-shadow:-10px 0 0 rgba(43,33,24,.16);
    translate:100% 0;
    /* Same discipline as the nav drawer in stitchobsession.css: translated alone
       the closed panel stays visibility:visible, so the whole abbreviations
       list — every term, every definition, and the close button — stayed in
       the tab order and in the accessibility tree on every article, parked
       360px off the right edge. The visibility flip is delayed on close so the
       slide still animates, and instant on open so a .focus() in the same tick
       is not a no-op. */
    visibility:hidden;
    transition:translate var(--t) var(--ease-out),
               visibility 0s linear var(--t);
  }
  .pattern-sidebar.open{
    translate:0 0;
    visibility:visible;
    transition:translate var(--t) var(--ease-out),
               visibility 0s linear 0s;
  }
  /* Belt and braces. The delayed visibility flip on close does not always
     land once the transition definition changes in the same tick, which left
     the panel visibility:visible off-canvas after an open/close cycle.
     closeLayer() sets `inert` synchronously, so key off that too — the two can
     never disagree. */
  .pattern-sidebar[inert]{ visibility:hidden; }
  .sidebar-close-wrap{ display:flex; justify-content:flex-end; margin-bottom:var(--sp-3); }
  .sidebar-close-btn{
    display:grid; place-items:center;
    width:48px; height:48px;
    font-family:var(--display); font-size:1.5rem; line-height:1;
    color:var(--on-red); background:var(--red);
    border:var(--bw) solid var(--line); border-radius:50%;
    box-shadow:var(--pop-sm);
    cursor:pointer;
  }
  .sidebar-close-btn:focus-visible{ outline:3px solid var(--ink); outline-offset:3px; }
  .mobile-abbrev-toggle{ display:inline-flex; }
  .abbreviations-card{ border:0; box-shadow:none; padding:0; background:transparent; }
}

@media (max-width:760px){
  .pattern-card{ padding:var(--sp-5) var(--sp-4); }
  .pattern-action-bar{ gap:var(--sp-2); }
  .pab-btn{ flex:1 1 46%; rotate:0deg; padding:12px var(--sp-3); }
  .hero-plate{ margin-inline:0; }
  .hero-roundel{ --roundel-size:74px; top:calc(-1 * var(--sp-4)); right:calc(-1 * var(--sp-2)); }
  .hero-thumbnail-grid[data-count="2"],
  .hero-thumbnail-grid[data-count="3"]{ grid-template-columns:1fr; }
  .why-love-images-grid{ grid-template-columns:1fr; }
  .hero-tile,
  .why-love-tile{ rotate:0deg; }
  .row-instruction{ padding:var(--sp-4) var(--sp-5) var(--sp-4) var(--sp-4); }
  .row-tick{ top:var(--sp-3); right:var(--sp-2); width:24px; height:24px; }
  /* (.footer-content--3 and .footer-legal at this width are stitchobsession.css
     §21's — identical copies lived here and were deleted.) */
  /* 84px clears the floating Abbreviations button that sits beside it; the
     banner term has to be carried through here too or the mobile override
     silently undoes the collision fix above. */
  .global-progress{
    left:var(--sp-3); right:var(--sp-3); width:auto;
    bottom:calc(84px + var(--sp-banner-h, 0px) + env(safe-area-inset-bottom));
  }
  .mobile-abbrev-toggle{
    right:var(--sp-3);
    bottom:calc(var(--sp-3) + var(--sp-banner-h, 0px) + env(safe-area-inset-bottom));
  }
  .complete-badge{ width:min(92vw, 420px); padding:var(--sp-4); }
  .content-title-mark{ width:34px; height:34px; font-size:.85rem; }
  .prose-block > p:first-of-type::first-letter{ font-size:2.6em; }
}


/* ═══════════════════════════════════════════════════════════════════════════
   15 · REDUCED MOTION + PRINT
   ═══════════════════════════════════════════════════════════════════════ */
@media (prefers-reduced-motion:reduce){
  .sp-loading-mark{ animation:none; }
  .hero-tile,
  .why-love-tile,
  .pab-btn,
  .content-title-mark,
  .hero-roundel,
  .qa-badge,
  .complete-badge{ rotate:0deg!important; }
  .hero-tile:hover,
  .why-love-tile:hover,
  .material-item:hover,
  .row-instruction[data-round]:hover{ translate:0 0!important; }
  .hero-tile-zoom{ opacity:1; scale:1; }
  .progress-bar-fill,
  .global-progress-bar-fill{ transition:none; }
}

/* ── PRINT ────────────────────────────────────────────────────────────────
   The Print button in the action bar prints THIS. What a crocheter needs on
   paper is the pattern and its abbreviations — not the masthead, the promo
   panels, the newsletter or the modals. Ink is black on white, every FAQ is
   opened, and no round is ever split across two pages. */
@media print{
  @page{ margin:14mm; }

  .navbar, .mobile-nav, .sp-tape, .sp-scallop, .sp-ribbon,
  .floating-elements, .decorative-elements, .float-item,
  .pattern-action-bar, .pab-toast, .skip-link,
  .newsletter-section, .footer-section, .back-row,
  .content-side,
  .global-progress, .complete-badge,
  .sp-modal, .mobile-overlay, .mobile-abbrev-toggle,
  .sidebar-close-wrap, .hero-roundel, .side-promo,
  .hero-tile-zoom, .row-tick, .material-tick,
  .progress-tracker-hint{
    display:none!important;
  }

  html, body{ background:#fff!important; color:#000!important; }

  /* Skill level and time-at-the-hook stay on the printout — they are the two
     things a crocheter checks before casting on. */
  .hero-section, .content-section, .pattern-section, .qa-section,
  .intro-section{
    padding-block:0!important; background:#fff!important;
  }
  .feature-card{
    background:#fff!important; border:1px solid #000!important;
    box-shadow:none!important; rotate:0deg!important;
    break-inside:avoid; page-break-inside:avoid;
  }
  .feature-icon{ display:none!important; }
  .section-rubric{ color:#000!important; text-shadow:none!important; }
  .content-grid, .pattern-content-wrapper{ display:block!important; }

  .hero-title, .pattern-title, .part-title, .content-title,
  .qa-title, .sp-h2, .row-label{
    color:#000!important; text-shadow:none!important;
  }

  .content-card, .pattern-card, .abbreviations-card,
  .row-instruction, .material-item, .progress-tracker, .conclusion,
  .tips-content-list li, .mistake-item{
    background:#fff!important; color:#000!important;
    border:1px solid #000!important;
    box-shadow:none!important;
    rotate:0deg!important;
  }
  .sp-framed::before{ display:none!important; }

  /* Never break a round, a material or a mistake across a page. */
  .row-instruction, .material-item, .mistake-item,
  .tips-content-list li, .qa-item, .abbrev-item, .progress-tracker{
    break-inside:avoid; page-break-inside:avoid;
  }
  .part-section{ break-before:auto; page-break-inside:auto; }
  .part-title{ break-after:avoid; page-break-after:avoid; }

  /* The count still has to read as the count on paper. */
  .row-count{
    color:#000!important; background:#fff!important;
    border:1.5px solid #000!important; box-shadow:none!important;
    font-weight:700;
  }
  .row-instruction.completed{ background:#f2f2f2!important; }
  .row-instruction.completed::after{ content:' ✓'; font-weight:700; }

  /* Abbreviations belong on the printout — as a block, after the pattern. */
  .pattern-sidebar{
    position:static!important; display:block!important;
    width:auto!important; translate:none!important;
    padding:0!important; margin-top:10mm;
    border:0!important; box-shadow:none!important;
    overflow:visible!important;
  }
  .pattern-sidebar[inert]{ pointer-events:auto; }
  .abbreviations-card{ max-height:none!important; display:block!important; }
  .abbrev-list{ overflow:visible!important; background:none!important; }

  /* Every answer opened — a printed accordion is just missing text. */
  .qa-list .qa-item .qa-answer{ display:block!important; }
  .qa-icon{ display:none!important; }

  .progress-bar-track{ border:1px solid #000!important; background:#fff!important; }
  .progress-bar-fill{ background:#999!important; }

  /* The printed-URL rule for external links is stitchobsession.css §22's; the
     same declaration sat here too. Internal anchors stay bare. */
  a[href^="#"]::after,
  a[href^="/"]::after{ content:""; }
}
