-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
2136 lines (2053 loc) · 137 KB
/
Copy pathindex.html
File metadata and controls
2136 lines (2053 loc) · 137 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="theme-color" content="#1D110D">
<link rel="manifest" href="manifest.json">
<link rel="icon" href="icon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="apple-touch-icon.png">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-title" content="Ribs">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<title>Smoked Ribs, Made Simple — A Science-Based Companion</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800&family=Oswald:wght@600;700&family=Zilla+Slab:wght@400;700&display=swap" rel="stylesheet">
<style>
/* ---------- Design tokens ---------- */
:root{
--bark-950:#0F0806; --bark-900:#1D110D; --bark-800:#2F1A15; --bark-700:#4A2A22; --bark-500:#734135;
--ember:#E06A3B; --ember-400:#F2875A; --ember-text:#9A3412;
--parch-50:#FAF8F5; --parch-100:#F4ECE1; --parch-200:#E6D7C3; --parch-300:#D8C4A8;
--ink:#1A1310; --muted:#6B5D52; --line:#E6D7C3;
--ok:#2E7D32; --warn:#B23A1E;
--radius:16px; --radius-sm:10px;
--shadow:0 10px 30px rgba(15,8,6,.10);
--shadow-lg:0 20px 45px rgba(15,8,6,.20);
--font-sans:"Inter",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
--font-serif:"Zilla Slab","Iowan Old Style",Georgia,"Times New Roman",serif;
--font-label:"Oswald","Inter",sans-serif;
--maxw:1120px;
}
/* ---------- Reset ---------- */
*,*::before,*::after{box-sizing:border-box}
[hidden]{display:none !important}
html{scroll-behavior:smooth}
body{margin:0;font-family:var(--font-sans);color:var(--ink);background:var(--parch-50);
line-height:1.6;font-size:17px;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}
h1,h2,h3,h4{font-family:var(--font-serif);line-height:1.15;margin:0}
p{margin:0}
a{color:inherit}
img,svg{max-width:100%;height:auto}
button{font-family:inherit;font-size:inherit;cursor:pointer}
input,select{font-family:inherit}
::selection{background:var(--ember-400);color:var(--bark-950)}
:focus-visible{outline:3px solid var(--ember);outline-offset:2px;border-radius:6px}
.wrap{max-width:var(--maxw);margin:0 auto;padding:0 20px}
.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}
.eyebrow{font-family:var(--font-label);font-size:.82rem;font-weight:600;letter-spacing:.15em;text-transform:uppercase;color:var(--ember-text)}
.on-dark .eyebrow{color:var(--ember)}
.brand small,.phase .meta,.field label,.salt-card .coeff,.readout-time small,#readout-title,
.timer-panel .eyebrow,.anat-btns button,.toggle-group button,.spritz-boxes small,.diag button.q,
.nav-links a,#mobile-menu a,.schedule .when{font-family:var(--font-label)}
.phase .meta{letter-spacing:.06em}
.lede{color:var(--muted);font-size:1.05rem;max-width:60ch}
.center{text-align:center}
.mx-auto{margin-left:auto;margin-right:auto}
/* ---------- Header / nav ---------- */
header.site{position:sticky;top:0;z-index:60;background:rgba(29,17,13,.96);color:var(--parch-100);
backdrop-filter:blur(8px);border-bottom:1px solid var(--bark-800)}
.nav{display:flex;align-items:center;justify-content:space-between;height:64px}
.brand{display:flex;align-items:center;gap:10px;text-decoration:none}
.brand .mark{width:34px;height:34px;color:var(--ember);flex:none}
.brand b{font-family:var(--font-serif);font-size:1.05rem;color:#fff;letter-spacing:.01em;display:block;line-height:1.1}
.brand small{font-size:.62rem;letter-spacing:.16em;text-transform:uppercase;color:var(--ember);font-weight:700}
.nav-links{display:flex;gap:22px;list-style:none;margin:0;padding:0}
.nav-links a{color:var(--parch-100);text-decoration:none;font-weight:600;font-size:.82rem;letter-spacing:.02em;padding:6px 2px;border-bottom:2px solid transparent}
.nav-links a:hover{color:var(--ember-400);border-color:var(--ember-400)}
.nav-toggle{display:none;background:none;border:1px solid var(--bark-700);color:var(--parch-100);
border-radius:10px;padding:8px 10px;align-items:center;gap:8px}
.nav-toggle svg{width:22px;height:22px}
#mobile-menu{display:none;background:var(--bark-900);border-top:1px solid var(--bark-800)}
#mobile-menu.open{display:block}
#mobile-menu ul{list-style:none;margin:0;padding:8px 20px 16px}
#mobile-menu a{display:block;color:var(--parch-100);text-decoration:none;font-weight:600;
padding:12px 4px;border-bottom:1px solid var(--bark-800)}
@media (max-width:860px){
.nav-links{display:none}
.nav-toggle{display:inline-flex}
}
/* ---------- Buttons ---------- */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;font-weight:700;
border-radius:12px;padding:13px 22px;border:1px solid transparent;text-decoration:none;transition:.15s}
.btn-primary{background:var(--ember);color:var(--bark-950)}
.btn-primary:hover{background:var(--ember-400)}
.btn-ghost{background:transparent;border-color:var(--bark-700);color:var(--parch-100)}
.btn-ghost:hover{background:var(--bark-800)}
.btn-dark{background:var(--bark-950);color:#fff;border-radius:10px;padding:9px 16px;font-size:.85rem}
.btn-dark:hover{background:var(--bark-800)}
.btn-sm{padding:8px 14px;font-size:.82rem;border-radius:10px}
.btn-outline{background:#fff;border:1px solid var(--line);color:var(--bark-800)}
.btn-outline:hover{background:var(--parch-50)}
/* ---------- Sections ---------- */
section{padding:64px 0;border-bottom:1px solid var(--line)}
.sec-head{text-align:center;max-width:64ch;margin:0 auto 40px}
.sec-head h2{font-size:2rem;color:var(--bark-950);margin-top:6px}
.sec-head p{color:var(--muted);margin-top:12px}
.bg-parch{background:var(--parch-100)}
.bg-white{background:#fff}
.card{background:#fff;border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow)}
.pad{padding:26px}
/* ---------- Hero ---------- */
.hero{background:
radial-gradient(75% 120% at 82% -15%, rgba(224,106,59,.28), transparent 55%),
radial-gradient(60% 90% at 8% 115%, rgba(224,106,59,.14), transparent 60%),
var(--bark-950);color:#fff;position:relative;overflow:hidden}
.hero::before{content:"";position:absolute;inset:0;opacity:.05;
background-image:radial-gradient(var(--ember) 1px,transparent 1px);background-size:22px 22px}
.hero .wrap{position:relative;display:grid;grid-template-columns:1.15fr .85fr;gap:44px;align-items:center;padding-top:56px;padding-bottom:56px}
.hero h1{font-size:clamp(2.2rem,5vw,3.6rem);color:#fff}
.hero h1 span{color:var(--ember)}
.hero p{color:var(--parch-100);font-size:1.12rem;margin-top:18px;max-width:46ch}
.hero .cta{display:flex;gap:14px;flex-wrap:wrap;margin-top:26px}
.hero .promise{display:flex;gap:18px;flex-wrap:wrap;margin-top:26px;color:var(--parch-200);font-size:.9rem}
.hero .promise b{color:#fff}
@media (max-width:860px){ .hero .wrap{grid-template-columns:1fr;gap:28px} }
/* ---------- Anatomy card ---------- */
.anat{background:var(--bark-900);border:1px solid var(--bark-800);border-radius:20px;padding:22px}
.anat h3{color:#fff;font-size:1.1rem}
.anat p.hint{color:var(--parch-200);font-size:.9rem;margin-top:6px}
.anat-btns{display:flex;flex-wrap:wrap;gap:8px;margin:14px 0}
.anat-btns button{background:var(--bark-800);color:var(--parch-100);border:1px solid var(--bark-700);
border-radius:999px;padding:7px 14px;font-size:.8rem;font-weight:700}
.anat-btns button[aria-pressed="true"]{background:var(--ember);color:var(--bark-950);border-color:var(--ember)}
#anatomy-readout{background:var(--bark-950);border:1px solid var(--bark-800);border-radius:12px;padding:16px;margin-top:12px;min-height:110px}
#readout-title{color:var(--ember);font-size:.72rem;font-weight:800;letter-spacing:.14em;text-transform:uppercase;display:block;margin-bottom:6px}
#readout-body{color:var(--parch-100);font-size:.95rem}
.svg-layer{cursor:pointer;transition:.2s}
.anat svg.has-sel .svg-layer:not(.sel){opacity:.4}
.svg-layer.sel{stroke:#F2875A;stroke-width:8;filter:drop-shadow(0 0 6px rgba(224,106,59,.95))}
#svg-bone.sel{stroke:#F2875A;stroke-width:5}
@media (prefers-reduced-motion:no-preference){ .svg-layer.sel{animation:anatPulse 1.6s ease-in-out infinite} }
@keyframes anatPulse{0%,100%{filter:drop-shadow(0 0 4px rgba(224,106,59,.6))}50%{filter:drop-shadow(0 0 9px rgba(224,106,59,1))}}
.anat svg{display:block;max-width:240px;margin:6px auto 2px}
.smoke-wisp{opacity:0;transform-box:fill-box;transform-origin:center}
@media (prefers-reduced-motion:no-preference){
.smoke-wisp{animation:smokeRise 4.5s ease-in-out infinite}
.smoke-wisp:nth-of-type(2){animation-delay:1.5s}
.smoke-wisp:nth-of-type(3){animation-delay:3s}
}
@keyframes smokeRise{0%{opacity:0;transform:translateY(10px) scaleX(.85)}25%{opacity:.5}70%{opacity:.22}100%{opacity:0;transform:translateY(-40px) scaleX(1.2)}}
@media (prefers-reduced-motion:reduce){ .smoke-wisp{opacity:.26} }
/* ---------- Steps grid ---------- */
.grid{display:grid;gap:18px}
.cols-2{grid-template-columns:1fr 1fr}
.cols-3{grid-template-columns:repeat(3,1fr)}
@media (max-width:760px){.cols-2,.cols-3{grid-template-columns:1fr}}
.step{display:flex;gap:16px;background:var(--parch-50);border:1px solid var(--line);border-radius:var(--radius);padding:22px}
.step .num{font-family:var(--font-serif);font-size:2.1rem;font-weight:800;color:var(--ember);line-height:1}
.step h4{color:var(--bark-950);font-size:1.05rem}
.step p{color:var(--muted);font-size:.92rem;margin-top:4px}
/* ---------- Planner ---------- */
.planner{display:grid;grid-template-columns:1fr 1.3fr;gap:26px}
@media (max-width:820px){.planner{grid-template-columns:1fr}}
.field{margin-bottom:18px}
.field label{display:block;font-size:.74rem;font-weight:800;text-transform:uppercase;letter-spacing:.08em;color:var(--bark-700);margin-bottom:8px}
.control{width:100%;background:var(--parch-50);border:1px solid var(--line);border-radius:12px;padding:12px 14px;font-size:1rem;font-weight:600;color:var(--ink)}
.control:focus{outline:none;border-color:var(--ember);box-shadow:0 0 0 3px rgba(224,106,59,.25)}
.schedule{list-style:none;margin:0;padding:0}
.schedule li{display:flex;gap:14px;align-items:flex-start;padding:14px 0;border-bottom:1px dashed var(--line)}
.schedule li:last-child{border-bottom:none}
.schedule .tick{width:34px;height:34px;flex:none;border-radius:10px;background:var(--bark-950);color:#fff;
display:flex;align-items:center;justify-content:center;font-family:var(--font-serif);font-weight:700;font-size:.8rem}
.schedule .when{font-weight:800;color:var(--bark-950)}
.schedule .what{color:var(--muted);font-size:.9rem}
.schedule li.serve .tick{background:var(--ember);color:var(--bark-950)}
.plan-empty{color:var(--muted);font-style:italic;padding:20px 0}
.schedule li.warn{background:#FDEEE6;border:1px solid #F3C9B4;border-radius:12px;padding:12px 14px;margin-bottom:6px}
.schedule li.warn .tick{background:var(--warn)}
.schedule li.warn .what{color:#8A3B1B;font-size:.92rem}
.schedule li.past .when,.schedule li.past .what{opacity:.55}
/* ---------- Scaler ---------- */
.toggle-group{display:flex;gap:10px}
.toggle-group button{flex:1;background:#fff;border:1px solid var(--line);border-radius:12px;padding:12px;font-weight:700;color:var(--bark-700)}
.toggle-group button[aria-pressed="true"]{border:2px solid var(--ember);background:#FEF3EC;color:var(--bark-950)}
.inline{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.inline .control{width:auto}
.w-sm{width:72px !important;text-align:center}
.w-md{width:92px !important;text-align:center}
.readout-time{background:var(--parch-50);border:1px solid var(--line);border-radius:12px;padding:12px;text-align:center}
.readout-time b{display:block;font-size:1.1rem;color:var(--bark-950)}
.readout-time small{color:var(--muted);font-size:.78rem}
.scaler-out{display:grid;grid-template-columns:5fr 7fr;gap:20px;margin-top:24px}
@media (max-width:820px){.scaler-out{grid-template-columns:1fr}}
.salt-card{background:var(--bark-950);color:#fff;border-radius:var(--radius);padding:24px;display:flex;flex-direction:column;justify-content:space-between}
.salt-card .big{font-size:2.6rem;font-weight:800}
.salt-card .unit{color:var(--parch-200);font-weight:600}
.salt-card p{color:var(--parch-200);font-size:.9rem;margin-top:10px}
.salt-card .coeff{background:var(--bark-900);color:var(--ember);font-size:.72rem;font-weight:700;letter-spacing:.08em;
text-align:center;padding:8px;border-radius:8px;margin-top:16px}
.rub-card{background:var(--parch-50);border:1px solid var(--line);border-radius:var(--radius);padding:24px}
.rub-row{display:flex;justify-content:space-between;gap:12px;padding:9px 0;border-bottom:1px dashed var(--line);font-size:.94rem}
.rub-row:last-of-type{border-bottom:none}
.rub-row .g{display:flex;flex-direction:column;align-items:flex-end;gap:1px;white-space:nowrap}
.rub-row .g-mass{font-variant-numeric:tabular-nums;font-weight:800}
.rub-row .vol{font-weight:600;font-size:.72rem;color:var(--muted)}
.spritz{margin-top:20px;background:var(--parch-50);border:1px solid var(--line);border-radius:var(--radius);padding:22px;
display:grid;grid-template-columns:5fr 7fr;gap:20px;align-items:center}
@media (max-width:760px){.spritz{grid-template-columns:1fr}}
.spritz-boxes{display:grid;grid-template-columns:1fr 1fr;gap:12px}
.spritz-boxes .box{background:#fff;border:1px solid var(--line);border-radius:12px;padding:14px;text-align:center}
.spritz-boxes small{color:var(--muted);font-size:.72rem;text-transform:uppercase;letter-spacing:.05em;display:block;margin-bottom:4px}
.spritz-boxes b{font-size:1.15rem;color:var(--bark-950)}
.shopping{margin-top:20px}
.shopping ul{columns:2;gap:24px;list-style:none;padding:0;margin:8px 0 0}
@media (max-width:600px){.shopping ul{columns:1}}
.shopping li{padding:6px 0;font-size:.92rem;color:var(--bark-800)}
.shopping li b{font-variant-numeric:tabular-nums}
/* ---------- Timeline ---------- */
.timeline{display:grid;grid-template-columns:7fr 5fr;gap:26px;align-items:start}
@media (max-width:900px){.timeline{grid-template-columns:1fr}}
.phase{background:#fff;border:1px solid var(--line);border-radius:var(--radius);padding:22px;margin-bottom:16px;transition:.2s;position:relative;overflow:hidden}
.phase.active{border:2px solid var(--ember);box-shadow:var(--shadow)}
.phase.done > *:not(.predictor){opacity:.5}
.phase-top{display:flex;justify-content:space-between;align-items:flex-start;gap:14px}
.phase-id{background:var(--bark-950);color:#fff;font-family:var(--font-serif);font-weight:700;font-size:.8rem;
width:34px;height:34px;border-radius:9px;display:flex;align-items:center;justify-content:center;flex:none}
.phase h4{color:var(--bark-950);font-size:1.08rem}
.phase .meta{font-size:.78rem;font-weight:700;letter-spacing:.03em;text-transform:uppercase;color:var(--ember-text);margin-top:3px}
.phase .plain{margin-top:14px;color:var(--bark-800);font-size:.96rem}
.phase .plain strong{color:var(--bark-950)}
.doneness{margin-top:12px;background:#FEF3EC;border:1px solid var(--parch-300);border-radius:10px;padding:12px 14px;font-size:.9rem;color:var(--bark-800)}
.doneness b{color:var(--ember-text)}
details.why{margin-top:12px;border-top:1px solid var(--line);padding-top:10px}
details.why summary{cursor:pointer;font-weight:700;color:var(--ember-text);font-size:.86rem;list-style:none;display:flex;align-items:center;gap:6px}
details.why summary::-webkit-details-marker{display:none}
details.why summary::before{content:"▸";transition:.15s}
details.why[open] summary::before{transform:rotate(90deg)}
details.why p{margin-top:8px;color:var(--muted);font-size:.9rem}
.phase-actions{margin-top:14px;display:flex;gap:10px;flex-wrap:wrap;align-items:center}
.phase-actions .btn-dark.armed{background:var(--ember);color:var(--bark-950);font-variant-numeric:tabular-nums}
.phase-actions .btn-dark.armed:hover{background:var(--ember-400)}
@keyframes chipPulse{0%,100%{box-shadow:0 0 0 0 rgba(224,106,59,0)}50%{box-shadow:0 0 0 6px rgba(224,106,59,.5)}}
.cb-timer.pulse{animation:chipPulse .9s ease-in-out 2}
.check{display:inline-flex;align-items:center;gap:9px;font-size:.85rem;font-weight:700;color:var(--bark-700);cursor:pointer}
.check input{width:22px;height:22px;accent-color:var(--ember);cursor:pointer}
/* Timer panel */
.timer-panel{background:var(--bark-900);border:1px solid var(--bark-800);color:#fff;border-radius:var(--radius);
padding:24px;position:sticky;top:80px;box-shadow:var(--shadow-lg)}
.timer-panel .eyebrow{color:var(--ember)}
.timer-panel h4{color:#fff;font-size:1.3rem;margin-top:4px;border-bottom:1px solid var(--bark-800);padding-bottom:14px}
.timer-display{text-align:center;padding:26px 0}
.timer-display span{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:3.1rem;font-weight:800;color:var(--ember);letter-spacing:.04em}
.timer-note{text-align:center;color:var(--parch-200);font-size:.8rem;margin-top:-10px;margin-bottom:8px}
.timer-row{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}
.timer-row span{color:var(--parch-200);font-weight:700;font-size:.9rem}
.timer-adj{display:flex;gap:8px}
.timer-adj button{background:var(--bark-800);color:#fff;border:none;border-radius:10px;padding:8px 12px;font-weight:700;font-size:.82rem}
.timer-adj button:hover{background:var(--bark-700)}
.timer-cta{display:grid;grid-template-columns:1fr 1fr;gap:10px}
.timer-cta .stop{background:transparent;border:1px solid var(--bark-700);color:var(--parch-200);border-radius:10px;padding:12px;font-weight:700}
.timer-test{text-align:center;margin-top:18px;border-top:1px solid var(--bark-800);padding-top:14px}
.timer-test button{background:none;border:none;color:var(--parch-300);text-decoration:underline;font-size:.82rem;font-weight:600}
/* Floating cook status bar */
#cook-bar{position:fixed;left:0;right:0;bottom:0;z-index:70;background:var(--bark-950);color:#fff;
border-top:2px solid var(--ember);padding:10px max(18px,env(safe-area-inset-left));display:none;align-items:center;
justify-content:space-between;gap:12px;box-shadow:0 -8px 24px rgba(0,0,0,.3)}
#cook-bar.show{display:flex}
.cb-jump{flex:1;min-width:0;display:flex;flex-direction:column;align-items:flex-start;gap:1px;background:none;border:none;
text-align:left;color:#fff;padding:4px 2px;cursor:pointer;border-radius:8px}
.cb-jump:hover{background:rgba(255,255,255,.06)}
.cb-eyebrow{font-family:var(--font-label);font-size:.62rem;letter-spacing:.14em;text-transform:uppercase;color:var(--ember);font-weight:700}
.cb-step{font-family:var(--font-serif);font-size:1.05rem;font-weight:700;color:#fff;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}
.cb-timer{display:inline-flex;align-items:center;gap:8px;background:var(--bark-800);border:1px solid var(--bark-700);
border-radius:10px;padding:7px 12px;cursor:pointer;flex:none}
.cb-timer:hover{background:var(--bark-700)}
.cb-timer .icon{width:16px;height:16px;stroke:var(--ember)}
.cb-timer span{font-family:ui-monospace,Menlo,monospace;font-size:1.25rem;font-weight:800;color:var(--ember)}
.cb-prog{display:flex;align-items:center;gap:8px;width:100%;margin-top:5px}
.cb-prog-bar{flex:1;height:5px;border-radius:4px;background:var(--bark-800);overflow:hidden;min-width:56px}
#cb-prog-fill{display:block;height:100%;width:0;background:var(--ember);border-radius:4px;transition:width .9s linear}
.cb-prog-label{font-family:ui-monospace,Menlo,monospace;font-size:.66rem;color:var(--parch-200);white-space:nowrap}
.cb-eta{display:flex;flex-direction:column;align-items:flex-end;line-height:1.2;flex:none}
.cb-eta-time{font-family:ui-monospace,Menlo,monospace;font-size:1.2rem;font-weight:800;color:var(--ember)}
.cb-eta-togo{font-family:var(--font-label);font-size:.64rem;color:var(--parch-200);white-space:nowrap}
.cb-next{background:var(--ember);color:var(--bark-950);border:none;border-radius:10px;padding:10px 12px;font-weight:800;font-size:.82rem;flex:none;display:inline-flex;align-items:center;gap:6px;cursor:pointer}
.cb-next:hover{background:var(--ember-400)}
.cb-next .icon{width:15px;height:15px;stroke-width:3}
body.has-cook-bar{padding-bottom:88px}
@media (max-width:640px){
#cook-bar{flex-wrap:wrap;gap:6px 8px;padding:8px 12px max(8px,env(safe-area-inset-bottom))}
.cb-jump{flex:1 1 100%}
.cb-eyebrow{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}
.cb-eta{flex-direction:row;align-items:baseline;gap:6px;margin-right:auto}
.cb-in{display:none}
.cb-eta-time{font-size:1.05rem}
.cb-timer span{font-size:1.05rem}
.cb-next .cb-next-label{display:none}
body.has-cook-bar{padding-bottom:126px}
}
/* Predictor coaching line */
.p-coach{margin-top:10px;font-size:.86rem;font-weight:700;border-radius:10px;padding:9px 12px;line-height:1.4}
.p-coach.behind{background:rgba(224,106,59,.16);border:1px solid rgba(224,106,59,.4);color:#F2B79A}
.p-coach.ahead{background:rgba(120,180,120,.12);border:1px solid rgba(120,180,120,.3);color:#BFE0BF}
/* Confirm dialog */
.dlg{border:none;border-radius:16px;padding:0;max-width:400px;width:92vw;box-shadow:var(--shadow-lg);color:var(--ink);background:#fff}
.dlg::backdrop{background:rgba(15,8,6,.55);backdrop-filter:blur(2px)}
.dlg-inner{padding:24px}
.dlg-inner h3{font-size:1.3rem;color:var(--bark-950)}
.dlg-inner p{color:var(--muted);font-size:.94rem;margin-top:8px}
.dlg-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:20px}
/* ---------- Bend simulator ---------- */
.bend-wrap{max-width:640px;margin:0 auto}
.bend-stage{background:#fff;border:1px solid var(--line);border-radius:var(--radius);padding:20px;display:flex;justify-content:center;align-items:center;height:240px;box-shadow:inset 0 2px 10px rgba(15,8,6,.06)}
#bend-drip{opacity:0}
#bend-drip.show{opacity:.9}
@keyframes dripFall{0%{transform:translateY(0)}15%{opacity:.9}100%{transform:translateY(30px);opacity:0}}
@media (prefers-reduced-motion:no-preference){ #bend-drip.show{animation:dripFall 1.7s ease-in infinite} }
@media (prefers-reduced-motion:reduce){ #bend-drip.show{opacity:.5} }
.bend-scale{display:flex;justify-content:space-between;font-size:.75rem;font-weight:800;color:var(--bark-700);margin:22px 0 10px}
#current-state-tag{font-family:ui-monospace,Menlo,monospace}
.tag-under{color:var(--muted)}
.tag-perfect{color:var(--ember-text)}
.tag-over{color:var(--warn)}
input[type=range]{width:100%;height:8px;border-radius:6px;background:var(--parch-200);accent-color:var(--ember)}
#bend-description{background:#fff;border:1px solid var(--line);border-radius:12px;padding:18px;margin-top:16px;text-align:center;
min-height:92px;display:flex;align-items:center;justify-content:center;color:var(--bark-700);font-size:.95rem}
/* ---------- Diagnostics ---------- */
.diag{border:1px solid var(--line);border-radius:var(--radius);overflow:hidden;margin-bottom:12px;box-shadow:var(--shadow)}
.diag button.q{width:100%;display:flex;justify-content:space-between;align-items:center;gap:14px;text-align:left;
background:var(--parch-50);border:none;padding:20px;font-weight:800;color:var(--bark-950);font-size:1rem}
.diag button.q:hover{background:var(--parch-100)}
.diag .arrow{width:22px;height:22px;flex:none;transition:transform .2s;color:var(--bark-700)}
.diag button.q[aria-expanded="true"] .arrow{transform:rotate(180deg)}
.diag .a{padding:0 20px;max-height:0;overflow:hidden;transition:max-height .25s ease}
.diag .a-inner{padding:6px 0 22px;color:var(--muted);font-size:.94rem}
.diag .a-inner p{margin-bottom:10px}
.diag .a-inner strong{color:var(--bark-950)}
/* ---------- Footer ---------- */
footer{background:var(--bark-950);color:var(--parch-100);padding:44px 0;border:none;text-align:center}
footer b{font-family:var(--font-serif);color:#fff;font-size:1.1rem}
footer p{color:var(--parch-300);font-size:.9rem;max-width:56ch;margin:12px auto 0}
/* ---------- Toast ---------- */
#toast{position:fixed;top:76px;left:50%;transform:translateX(-50%) translateY(-160%);z-index:90;
background:var(--bark-950);color:#fff;border:1px solid var(--ember);border-radius:12px;padding:14px 20px;
font-weight:700;box-shadow:var(--shadow-lg);transition:transform .3s,opacity .3s;max-width:90vw;text-align:center;
opacity:0;visibility:hidden;pointer-events:none}
#toast.show{transform:translateX(-50%) translateY(0);opacity:1;visibility:visible}
/* ---------- Icons ---------- */
.icon{width:1em;height:1em;display:inline-block;vertical-align:-.125em;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.sec-ic{width:56px;height:56px;border-radius:16px;display:flex;align-items:center;justify-content:center;
background:linear-gradient(160deg,#FCEBE0,#F4D6C4);color:var(--ember-text);border:1px solid var(--parch-300);
margin:0 auto 16px;box-shadow:var(--shadow)}
.sec-ic .icon{width:28px;height:28px;stroke-width:1.8}
.on-dark .sec-ic{background:var(--bark-800);color:var(--ember);border-color:var(--bark-700)}
.phase-wm{position:absolute;top:-12px;right:-12px;width:118px;height:118px;color:var(--ember);opacity:.06;pointer-events:none}
.phase-wm .icon{width:100%;height:100%;stroke-width:1.4}
.phase.active .phase-wm{opacity:.13}
.promise-ic{width:18px;height:18px;stroke:var(--ember);vertical-align:-.2em;margin-right:2px}
/* ---------- Photo slots ---------- */
.photo{position:relative;border-radius:var(--radius);overflow:hidden;border:1px solid var(--parch-300);
box-shadow:var(--shadow);margin:0;background:#1D110D}
.photo img{position:absolute;inset:0;display:block;width:100%;height:100%;object-fit:cover}
.photo .ph-fallback{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;
gap:10px;color:var(--parch-200);text-align:center;padding:18px;
background:radial-gradient(130% 130% at 28% 18%, rgba(224,106,59,.20), transparent 60%),
repeating-linear-gradient(118deg,#241009 0 15px,#2c150c 15px 30px)}
.photo .ph-fallback .icon{width:36px;height:36px;opacity:.85;stroke-width:1.6}
.photo .ph-fallback small{font-family:var(--font-label);letter-spacing:.05em;text-transform:uppercase;font-size:.72rem;opacity:.9;line-height:1.4}
.ph-fallback .dg-ill{width:92%;max-width:250px}
.photo:has(img[src]:not([src=""])) .ph-fallback{display:none}
.ratio-4x3{aspect-ratio:4/3}
.doneness-gallery{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;margin-top:30px;max-width:640px;margin-left:auto;margin-right:auto}
@media (max-width:640px){.doneness-gallery{grid-template-columns:1fr}}
.dg-item .photo{margin-bottom:10px}
.dg-item figcaption{text-align:center;font-family:var(--font-label);text-transform:uppercase;letter-spacing:.05em;font-size:.78rem;font-weight:600}
.dg-under figcaption{color:var(--muted)}
.dg-perfect figcaption{color:var(--ember-text)}
.dg-over figcaption{color:var(--warn)}
/* ---------- Sub-steps, callouts, predictor ---------- */
.phase-check{width:32px;height:32px;border-radius:9px;border:2px solid var(--parch-300);background:#fff;flex:none;
display:flex;align-items:center;justify-content:center;color:transparent;transition:.15s;cursor:pointer;padding:0}
.phase-check:hover{border-color:var(--ember)}
.phase-check .icon{width:18px;height:18px;stroke-width:3}
.phase.done .phase-check{background:var(--ember);border-color:var(--ember);color:var(--bark-950)}
.substeps{margin-top:14px;display:grid;gap:9px}
.substep{display:flex;gap:11px;align-items:flex-start;font-size:.94rem;color:var(--bark-800);cursor:pointer;
background:var(--parch-50);border:1px solid var(--line);border-radius:10px;padding:10px 12px;
transition:border-color .3s, box-shadow .3s}
.substep:hover{border-color:var(--parch-300)}
.substep.flash{border-color:var(--ember);box-shadow:0 0 0 3px rgba(224,106,59,.3)}
/* Recurring spritz row — a rhythm you keep, not a box you tick */
.spritz-step{background:#EEF3F7;border-color:#D3DEE8;align-items:center;flex-wrap:wrap;cursor:default}
.spritz-step .label{flex:1;min-width:180px;color:#3A4B5C}
.spritz-ic{width:19px;height:19px;flex:none;stroke:#4A6076}
.spritz-toggle{flex:none;margin-left:auto;background:#fff;border:1px solid #C7D3DE;border-radius:999px;padding:7px 13px;
font-family:var(--font-label);font-size:.7rem;font-weight:700;letter-spacing:.05em;text-transform:uppercase;
color:var(--bark-700);white-space:nowrap;font-variant-numeric:tabular-nums;cursor:pointer}
.spritz-toggle:hover{border-color:var(--ember);color:var(--ember-text)}
.spritz-toggle[aria-pressed="true"]{background:var(--ember);border-color:var(--ember);color:var(--bark-950)}
.substep input{margin-top:2px;width:19px;height:19px;accent-color:var(--ember);flex:none;cursor:pointer}
.substep .label{line-height:1.4}
.substep input:checked ~ .label{color:var(--muted);text-decoration:line-through;text-decoration-color:var(--parch-300)}
.crit-badge{display:inline-block;font-family:var(--font-label);font-size:.6rem;font-weight:700;letter-spacing:.06em;
text-transform:uppercase;color:#fff;background:var(--ember-text);border-radius:5px;padding:1px 6px;margin-left:4px;vertical-align:2px;white-space:nowrap}
.substep input:checked ~ .label .crit-badge{opacity:.35}
.callout{display:flex;gap:11px;margin-top:12px;border-radius:12px;padding:12px 14px;font-size:.92rem;line-height:1.5}
.callout .icon{width:20px;height:20px;flex:none;margin-top:2px;stroke-width:2}
.callout b{font-weight:800}
.callout.tip{background:#EDF6ED;border:1px solid #CDE6CD;color:#2C5D2E}
.callout.tip .icon{stroke:#2E7D32}
.callout.gotcha{background:#FDEEE6;border:1px solid #F3C9B4;color:#8A3B1B}
.callout.gotcha .icon{stroke:var(--ember-text)}
.callout.clean{background:#EEF2F6;border:1px solid #D3DEE8;color:#3A4B5C}
.callout.clean .icon{stroke:#4A6076}
.predictor{position:relative;overflow:hidden;background:var(--bark-950);color:#fff;border-radius:var(--radius);
padding:22px 26px;margin-bottom:26px;display:flex;gap:22px;align-items:center;justify-content:space-between;
flex-wrap:wrap;box-shadow:var(--shadow-lg)}
.predictor .phase-wm{color:var(--ember);opacity:.08}
.p-main{position:relative;z-index:1;min-width:min(100%,320px)}
.p-eyebrow{color:var(--ember);display:block;margin-bottom:4px}
.p-when{font-family:var(--font-serif);font-size:1.9rem;color:#fff;line-height:1.1}
.p-when.live{color:var(--ember)}
.p-sub{color:var(--parch-200);font-size:.9rem;margin-top:6px;max-width:54ch}
.p-side{position:relative;z-index:1;display:flex;flex-direction:column;gap:6px;align-items:flex-end}
.p-progress{width:210px}
.p-bar{height:9px;border-radius:6px;background:var(--bark-800);overflow:hidden}
.p-bar span{display:block;height:100%;width:0;background:var(--ember);border-radius:6px;transition:width .6s}
.p-reset{background:none;border:none;color:var(--parch-300);font-size:.74rem;text-decoration:underline;cursor:pointer;font-family:var(--font-label);padding:2px}
@media (max-width:640px){ .predictor{padding:20px} .p-side{align-items:stretch;width:100%} .p-progress{width:100%} .p-reset{text-align:right} }
/* Compact predictor (lives inside the P1 card) */
.predictor.compact{padding:16px 18px;margin:16px 0;gap:14px;box-shadow:none;border:1px solid var(--bark-800);display:block}
.predictor .eyebrow{color:var(--ember)}
.predictor.compact .p-when{font-size:1.35rem}
.predictor.compact .p-sub{font-size:.82rem;max-width:52ch;color:var(--parch-200);margin-top:6px}
.p-idle{display:flex;gap:14px;align-items:center;justify-content:space-between;flex-wrap:wrap}
.p-idle .p-main{min-width:min(100%,240px)}
/* Live tracker */
.p-live{width:100%}
.pl-grid{display:flex;gap:26px;flex-wrap:wrap}
.pl-stat{display:flex;flex-direction:column;gap:1px}
.pl-clock{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:1.55rem;font-weight:800;color:#fff;line-height:1.05}
.pl-clock.ember{color:var(--ember)}
.pl-togo{font-size:.74rem;color:var(--parch-200);font-family:var(--font-label)}
.pl-now{margin-top:14px;padding-top:14px;border-top:1px solid var(--bark-800)}
.pl-now-head{display:flex;justify-content:space-between;align-items:baseline;gap:12px}
.pl-phase{font-family:var(--font-serif);font-weight:700;color:#fff;font-size:1.05rem}
.pl-phase-time{font-size:.72rem;color:var(--parch-200);font-family:var(--font-label);white-space:nowrap;font-variant-numeric:tabular-nums}
.pl-bar{height:8px;border-radius:6px;background:var(--bark-800);overflow:hidden;margin-top:8px}
.pl-bar span{display:block;height:100%;width:0;background:var(--ember);border-radius:6px;transition:width .9s linear}
.pl-next{margin-top:9px;font-size:.84rem;color:var(--ember-400);font-weight:700}
.p-live .p-coach{margin-top:12px}
.p-live .p-reset{margin-top:12px}
/* ---------- Print ---------- */
@media print{
header.site,.hero,#deflection-visualizer,#troubleshooter,.timer-panel,#cook-bar,.nav-toggle,.phase-actions{display:none !important}
body{font-size:12px}
section{padding:16px 0;border:none}
.phase{break-inside:avoid;border:1px solid #ccc;box-shadow:none}
}
@media (prefers-reduced-motion:reduce){*{transition:none !important;scroll-behavior:auto}}
</style>
</head>
<body>
<!-- ============ ICON SPRITE ============ -->
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
<defs>
<symbol id="ic-calendar" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></symbol>
<symbol id="ic-scale" viewBox="0 0 24 24"><path d="m16 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z"/><path d="m2 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z"/><path d="M7 21h10"/><path d="M12 3v18"/><path d="M3 7h2c2 0 5-1 7-2 2 1 5 2 7 2h2"/></symbol>
<symbol id="ic-flame" viewBox="0 0 24 24"><path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"/></symbol>
<symbol id="ic-bend" viewBox="0 0 24 24"><path d="M3 9c4 0 5 8 9 8s5-8 9-8"/><circle cx="3" cy="9" r="1"/><circle cx="21" cy="9" r="1"/></symbol>
<symbol id="ic-wrench" viewBox="0 0 24 24"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></symbol>
<symbol id="ic-salt" viewBox="0 0 24 24"><path d="M7 21h10a1 1 0 0 0 1-1v-8a6 6 0 0 0-12 0v8a1 1 0 0 0 1 1Z"/><line x1="9.5" y1="7.5" x2="9.5" y2="7.51"/><line x1="12" y1="7" x2="12" y2="7.01"/><line x1="14.5" y1="7.5" x2="14.5" y2="7.51"/><line x1="6" y1="16" x2="18" y2="16"/></symbol>
<symbol id="ic-smoke" viewBox="0 0 24 24"><path d="M8 21c1.5-1.6-1.5-3.2 0-4.8s-1.5-3.2 0-4.8"/><path d="M13 21c1.5-1.6-1.5-3.2 0-4.8s-1.5-3.2 0-4.8"/><path d="M18 21c1.5-1.6-1.5-3.2 0-4.8s-1.5-3.2 0-4.8"/><path d="M6 8a4 4 0 0 1 8-.5A3 3 0 0 1 15 13"/></symbol>
<symbol id="ic-droplet" viewBox="0 0 24 24"><path d="M12 2.69l5.66 5.66a8 8 0 1 1-11.31 0z"/></symbol>
<symbol id="ic-wind" viewBox="0 0 24 24"><path d="M9.59 4.59A2 2 0 1 1 11 8H2"/><path d="M12.59 19.41A2 2 0 1 0 14 16H2"/><path d="M17.73 7.73A2.5 2.5 0 1 1 19.5 12H2"/></symbol>
<symbol id="ic-package" viewBox="0 0 24 24"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/></symbol>
<symbol id="ic-thermometer" viewBox="0 0 24 24"><path d="M14 4v10.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0Z"/></symbol>
<symbol id="ic-clock" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></symbol>
<symbol id="ic-wifi-off" viewBox="0 0 24 24"><line x1="1" y1="1" x2="23" y2="23"/><path d="M16.72 11.06A10.94 10.94 0 0 1 19 12.55"/><path d="M5 12.55a10.94 10.94 0 0 1 5.17-2.39"/><path d="M10.71 5.05A16 16 0 0 1 22.58 9"/><path d="M1.42 9a15.91 15.91 0 0 1 4.7-2.88"/><path d="M8.53 16.11a6 6 0 0 1 6.95 0"/><line x1="12" y1="20" x2="12.01" y2="20"/></symbol>
<symbol id="ic-check" viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></symbol>
<symbol id="ic-bulb" viewBox="0 0 24 24"><path d="M9 18h6"/><path d="M10 22h4"/><path d="M15.09 14c.18-.98.65-1.74 1.41-2.5A4.65 4.65 0 0 0 18 8 6 6 0 0 0 6 8c0 1 .23 2.23 1.5 3.5.76.76 1.23 1.52 1.41 2.5"/></symbol>
<symbol id="ic-alert" viewBox="0 0 24 24"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></symbol>
<symbol id="ic-sparkle" viewBox="0 0 24 24"><path d="M12 3l1.9 4.6L18.5 9.5 13.9 11.4 12 16l-1.9-4.6L5.5 9.5 10.1 7.6z"/><path d="M18 14.5l.65 1.85L20.5 17l-1.85.65L18 19.5l-.65-1.85L15.5 17l1.85-.65z"/></symbol>
</defs>
</svg>
<!-- ============ HEADER ============ -->
<header class="site">
<div class="wrap nav">
<a class="brand" href="#top">
<svg class="mark" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"/>
</svg>
<span><b>Smoked Ribs Companion</b><small>Science-based · no folklore</small></span>
</a>
<nav aria-label="Primary">
<ul class="nav-links">
<li><a href="#rig-scaler">Set Up</a></li>
<li><a href="#planner">Plan Time</a></li>
<li><a href="#cook-companion">Cook Steps</a></li>
<li><a href="#deflection-visualizer">Bend Test</a></li>
<li><a href="#troubleshooter">Fixes</a></li>
</ul>
</nav>
<button class="nav-toggle" id="nav-toggle" aria-expanded="false" aria-controls="mobile-menu">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path stroke-linecap="round" d="M4 6h16M4 12h16M4 18h16"/></svg>
<span class="sr-only">Menu</span>
</button>
</div>
<div id="mobile-menu">
<ul>
<li><a href="#rig-scaler">Set Up Your Cook</a></li>
<li><a href="#planner">Plan the Timing</a></li>
<li><a href="#cook-companion">Cook Steps & Timers</a></li>
<li><a href="#deflection-visualizer">The Bend Test</a></li>
<li><a href="#troubleshooter">Troubleshooting</a></li>
</ul>
</div>
</header>
<!-- ============ HERO ============ -->
<section class="hero on-dark" id="top" style="border-bottom:1px solid var(--bark-900)">
<div class="wrap">
<div>
<span class="eyebrow">The whole cook, one page</span>
<h1>Great smoked ribs, <span>without the guesswork</span></h1>
<p>Plain steps you can follow at the smoker, with the science tucked underneath for when you're curious. Works offline, remembers where you are, and tells you exactly when to start so dinner lands on time.</p>
<div class="cta">
<a href="#rig-scaler" class="btn btn-primary">Set up your cook</a>
<a href="#cook-companion" class="btn btn-ghost">Jump to the steps</a>
</div>
<div class="promise">
<span><svg class="icon promise-ic"><use href="#ic-clock"/></svg> <b>2‑day recipe</b> — 1 day of resting, <span id="hero-cook-hrs">~7</span> hrs cook</span>
<span><svg class="icon promise-ic"><use href="#ic-wifi-off"/></svg> <b>Installable</b> & works fully offline</span>
<span><svg class="icon promise-ic"><use href="#ic-bend"/></svg> <b>Doneness by feel</b>, not the clock</span>
</div>
</div>
<!-- Anatomy card -->
<div class="anat" id="anatomical-mapping">
<h3>What's happening inside a rib</h3>
<p class="hint">Tap a layer — or a button — to see what each part does while it cooks.</p>
<div class="anat-btns" role="group" aria-label="Rib layers">
<button type="button" data-anat="meat" aria-pressed="true">Muscle core</button>
<button type="button" data-anat="smoke">Smoke ring</button>
<button type="button" data-anat="bark">Bark</button>
<button type="button" data-anat="bone">Bone</button>
</div>
<svg viewBox="0 -80 400 380" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Cross-section of a pork rib showing muscle, smoke ring, bark and bone, with smoke rising off the top">
<defs>
<linearGradient id="meatGrad" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#703225"/><stop offset="1" stop-color="#8C4234"/></linearGradient>
<linearGradient id="smokeGrad" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#DC5F73"/><stop offset="1" stop-color="#E58A97"/></linearGradient>
<linearGradient id="boneGrad" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#ECE8DF"/><stop offset="1" stop-color="#D5CDBF"/></linearGradient>
</defs>
<!-- rising smoke (decorative) -->
<g class="smoke" aria-hidden="true" pointer-events="none" stroke="#CDBBA6" stroke-width="3.5" stroke-linecap="round" fill="none">
<path class="smoke-wisp" d="M168,40 C156,16 178,2 166,-26 C156,-46 172,-60 164,-78"/>
<path class="smoke-wisp" d="M205,36 C217,12 197,-2 209,-30 C219,-50 203,-64 212,-80"/>
<path class="smoke-wisp" d="M242,42 C232,20 250,4 240,-24 C232,-44 246,-58 238,-74"/>
</g>
<!-- muscle body (base) -->
<path id="svg-meat" class="svg-layer" data-anat="meat" tabindex="0" role="button" aria-label="Muscle core"
d="M 80,60 C 130,40 270,40 320,60 C 350,110 350,190 320,240 C 270,260 130,260 80,240 C 50,190 50,110 80,60 Z" fill="url(#meatGrad)" stroke="#3D1A13" stroke-width="3"/>
<!-- marbling + gloss (decorative) -->
<g pointer-events="none" fill="none">
<g stroke="#F4ECE1" stroke-opacity=".32" stroke-width="2.5" stroke-linecap="round">
<path d="M118,96 Q140,116 127,142"/>
<path d="M286,104 Q262,128 277,160"/>
<path d="M108,176 Q134,190 119,214"/>
<path d="M262,182 Q286,196 272,222"/>
</g>
<ellipse cx="150" cy="96" rx="78" ry="32" fill="#FFFFFF" opacity=".06"/>
</g>
<!-- smoke ring (myoglobin zone) -->
<path id="svg-smoke" class="svg-layer" data-anat="smoke" tabindex="0" role="button" aria-label="Smoke ring"
d="M 85,65 C 131,46 269,46 315,65 C 342,110 342,190 315,235 C 269,254 131,254 85,235 C 58,190 58,110 85,65 Z" fill="none" stroke="url(#smokeGrad)" stroke-width="10" stroke-opacity=".85"/>
<!-- bark (outer crust) -->
<path id="svg-bark" class="svg-layer" data-anat="bark" tabindex="0" role="button" aria-label="Bark"
d="M 80,60 C 130,40 270,40 320,60 C 350,110 350,190 320,240 C 270,260 130,260 80,240 C 50,190 50,110 80,60 Z" fill="none" stroke="#210A05" stroke-width="5"/>
<!-- bone with marrow -->
<rect id="svg-bone" class="svg-layer" data-anat="bone" tabindex="0" role="button" aria-label="Bone" x="175" y="100" width="50" height="100" rx="25" fill="url(#boneGrad)" stroke="#AFA79A" stroke-width="3"/>
<ellipse cx="200" cy="150" rx="13" ry="32" fill="#752C21" opacity=".8" pointer-events="none"/>
</svg>
<div id="anatomy-readout" aria-live="polite">
<span id="readout-title">Muscle core</span>
<p id="readout-body">Mostly water and protein. Salting a day ahead helps these fibers hold onto their own moisture, so the meat stays juicy instead of drying out.</p>
</div>
</div>
</div>
</section>
<!-- ============ SCALER ============ -->
<section id="rig-scaler" class="bg-parch">
<div class="wrap">
<div class="sec-head">
<span class="sec-ic"><svg class="icon"><use href="#ic-scale"/></svg></span>
<span class="eyebrow">Start here</span>
<h2>Set up your cook</h2>
<p>Enter your cut and weight once, right here. Everything downstream scales from it — your salt, rub and spritz amounts, a printable shopping list, and your backward schedule.</p>
</div>
<div class="card pad">
<div class="callout tip" style="margin-bottom:22px">
<svg class="icon"><use href="#ic-bulb"/></svg>
<div><b>Do this first:</b> before you toss the packaging, jot down the <b>total weight printed on the label</b> — that's what to enter below. It's the easy way to size everything exactly, instead of wrestling a scale with wet, slippery ribs later.<br>
<span style="opacity:.85;font-size:.86rem">Rough guide, per rack: baby back <b>1.5–2 lb</b> · St. Louis <b>2.5–3 lb</b> · full spares <b>3–4 lb</b>. Warehouse packs run <b>~5–9 lb</b> for 2–3 racks.</span></div>
</div>
<div class="grid cols-3" style="border-bottom:1px solid var(--line);padding-bottom:24px;margin-bottom:8px">
<div class="field" style="margin:0">
<label>1. Rib cut</label>
<div class="toggle-group" role="group" aria-label="Rib cut">
<button type="button" id="cut-stlouis" aria-pressed="true" onclick="setCut('stlouis')">St. Louis</button>
<button type="button" id="cut-babyback" aria-pressed="false" onclick="setCut('babyback')">Baby Backs</button>
</div>
</div>
<div class="field" style="margin:0">
<label for="num-slabs">2. Racks & total package weight</label>
<div class="inline">
<input type="number" id="num-slabs" class="control w-sm" value="2" min="1" max="12" oninput="calculateRig()" aria-label="Number of racks">
<span style="color:var(--muted);font-weight:600">racks ·</span>
<input type="number" id="total-weight" class="control w-sm" value="6.0" min="1" max="20" step="0.1" oninput="calculateRig()" aria-label="Total package weight">
<select id="weight-unit" class="control" style="width:auto" onchange="calculateRig()" aria-label="Weight unit"><option value="lbs">lbs</option><option value="kg">kg</option></select>
</div>
<p id="perslab-hint" style="color:var(--muted);font-size:.78rem;margin-top:6px">≈ 3.0 lb per rack</p>
</div>
<div class="field" style="margin:0">
<label for="salt-percent">3. Seasoning level</label>
<select id="salt-percent" class="control" onchange="calculateRig()">
<option value="0.006">Light (0.6% salt)</option>
<option value="0.0075" selected>Balanced (0.75% salt)</option>
<option value="0.009">Well-seasoned (0.9% salt)</option>
</select>
</div>
</div>
<div class="readout-time" style="margin-bottom:8px">
<b id="est-time-range">7.0 to 7.8 hours</b>
<small id="cut-metadata">Active cook time on the smoker (plus a ~40 min rest)</small>
</div>
<div class="scaler-out">
<div class="salt-card">
<div>
<span class="eyebrow" style="color:var(--ember)">Night before · dry brine</span>
<h4 style="color:#fff;font-size:1.2rem;margin-top:4px;border-bottom:1px solid var(--bark-900);padding-bottom:10px">Kosher salt</h4>
</div>
<div style="margin:18px 0">
<span class="big" id="calc-salt">20.4</span> <span class="unit">grams</span>
<div class="salt-vol" id="calc-salt-v" style="color:var(--parch-200);font-size:.82rem;font-weight:600;margin-top:3px">≈ 2½ tbsp Diamond Crystal · 1½ tbsp Morton</div>
<p>Sprinkle evenly over both sides and refrigerate uncovered overnight. This is the <em>only</em> salt in the recipe — the rub below is salt-free so you stay in control.</p>
</div>
<div class="coeff" id="salt-coeff">0.75% of total meat weight</div>
</div>
<div class="rub-card">
<span class="eyebrow">Rub the morning of · salt-free</span>
<h4 style="color:var(--bark-950);font-size:1.2rem;margin-top:4px;border-bottom:1px solid var(--line);padding-bottom:10px">Spice blend</h4>
<div style="margin-top:14px">
<div class="rub-row"><span>Coarse (16-mesh) black pepper — grip & bark texture</span><span class="g"><span class="g-mass"><span id="calc-pepper">27</span> g</span><span class="vol" id="calc-pepper-v"></span></span></div>
<div class="rub-row"><span>Paprika — deep mahogany color</span><span class="g"><span class="g-mass"><span id="calc-paprika">15</span> g</span><span class="vol" id="calc-paprika-v"></span></span></div>
<div class="rub-row"><span>Granulated garlic — savory depth</span><span class="g"><span class="g-mass"><span id="calc-garlic">9</span> g</span><span class="vol" id="calc-garlic-v"></span></span></div>
<div class="rub-row"><span>Granulated onion — background aromatics</span><span class="g"><span class="g-mass"><span id="calc-onion">6</span> g</span><span class="vol" id="calc-onion-v"></span></span></div>
<div class="rub-row"><span>MSG or mushroom powder — umami (optional)</span><span class="g"><span class="g-mass"><span id="calc-umami">3</span> g</span><span class="vol" id="calc-umami-v"></span></span></div>
</div>
<p style="color:var(--muted);font-size:.85rem;margin-top:12px">These amounts are the <b>whole batch</b> — split it evenly between racks, roughly ⅔ on the meat side and ⅓ on the bone side.</p>
<div id="ratio-descriptor" style="margin-top:14px;font-size:.72rem;font-weight:800;letter-spacing:.05em;text-transform:uppercase;color:var(--ember-text);background:#fff;border:1px solid var(--line);border-radius:10px;padding:8px;text-align:center">
About 1.8% of meat weight for St. Louis
</div>
</div>
</div>
<div class="spritz">
<div>
<span class="eyebrow">Your spritz</span>
<h4 style="color:var(--bark-950);font-size:1.15rem;margin-top:4px">Beer & cider blend</h4>
<p style="color:var(--muted);font-size:.9rem;margin-top:8px">Mix this in a spray bottle — you'll reach for it every hour or so through the first two phases, whenever the surface looks dry. The beer and cider sugars boost browning; plain water works too if you'd rather keep it simple.</p>
</div>
<div class="spritz-boxes">
<div class="box"><small>60% light beer</small><b id="calc-beer">12 oz</b></div>
<div class="box"><small>40% apple cider</small><b id="calc-cider">8 oz</b></div>
</div>
</div>
<div class="shopping card" style="padding:22px;box-shadow:none;background:var(--parch-50)">
<div style="display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap">
<h4 style="color:var(--bark-950);font-size:1.05rem">Shopping & prep list</h4>
<div style="display:flex;gap:8px;flex-wrap:wrap">
<button class="btn btn-outline btn-sm" id="copy-list-btn" type="button">📋 Copy</button>
<button class="btn btn-outline btn-sm" id="share-list-btn" type="button" hidden>↗ Share</button>
<button class="btn btn-outline btn-sm" type="button" onclick="window.print()">🖨 Print</button>
</div>
</div>
<ul id="shopping-list"></ul>
</div>
</div>
</div>
</section>
<!-- ============ REVERSE PLANNER ============ -->
<section id="planner" class="bg-white">
<div class="wrap">
<div class="sec-head">
<span class="sec-ic"><svg class="icon"><use href="#ic-calendar"/></svg></span>
<span class="eyebrow">Plan the timing</span>
<h2>When do you want to eat?</h2>
<p>Ribs are a two-day project — the salt goes on the night before. Tell us your target dinner time and we'll work backward so every step lands on schedule.</p>
</div>
<div class="card pad">
<div class="planner">
<div>
<div class="field">
<label for="plan-datetime">Serve time</label>
<input type="datetime-local" id="plan-datetime" class="control">
</div>
<div class="field">
<label>Your cook</label>
<div class="control" style="display:flex;align-items:center;justify-content:space-between;gap:10px;background:var(--parch-50)">
<span id="plan-context" style="font-weight:700">2 racks · 6 lb · St. Louis</span>
<a href="#rig-scaler" style="font-size:.78rem;font-weight:700;color:var(--ember-text);white-space:nowrap">change ↑</a>
</div>
<p style="color:var(--muted);font-size:.78rem;margin-top:6px">Pulled from the setup above — change your cut or weight there and the schedule updates automatically.</p>
</div>
<button class="btn btn-primary" id="plan-run" style="width:100%">Build my schedule</button>
<p style="color:var(--muted);font-size:.8rem;margin-top:12px">Your plan is saved on this device — close the tab and it'll still be here.</p>
</div>
<div>
<h3 style="color:var(--bark-950);font-size:1.15rem;margin-bottom:10px">Your backward schedule</h3>
<ol class="schedule" id="plan-output" aria-live="polite">
<li class="plan-empty">Enter a serve time and tap “Build my schedule”.</li>
</ol>
</div>
</div>
</div>
</div>
</section>
<!-- ============ COOK COMPANION ============ -->
<section id="cook-companion" class="bg-parch">
<div class="wrap">
<div class="sec-head">
<span class="sec-ic"><svg class="icon"><use href="#ic-flame"/></svg></span>
<span class="eyebrow">The cook, step by step</span>
<h2>Follow along at the smoker</h2>
<p>Do each step, check it off (your progress is saved), and set a reminder if you want a nudge. <strong>Times are estimates</strong> — the ribs are done when they pass the bend test below, not when a timer beeps.</p>
<button class="btn btn-outline btn-sm" id="new-cook-btn" type="button" style="margin-top:6px">↻ Start a new cook</button>
</div>
<div class="timeline">
<div id="phase-list">
<!-- P0 -->
<div class="phase active" id="card-p0">
<svg class="phase-wm icon"><use href="#ic-salt"/></svg>
<div class="phase-top">
<div style="display:flex;gap:14px">
<span class="phase-id">P0</span>
<div><h4>The night before: salt & rest</h4><p class="meta">12–24 hrs ahead · in the fridge (≈38°F / 3°C)</p></div>
</div>
<button class="phase-check" data-phasecheck="p0" aria-pressed="false" title="Mark this step done"><svg class="icon"><use href="#ic-check"/></svg><span class="sr-only">Mark step done</span></button>
</div>
<p class="plain">Peel or score the membrane, salt both sides, and rest uncovered overnight. To peel: slide a butter knife under it, grab with a paper towel and pull — or just score a tight cross-hatch through it with a sharp knife.</p>
<div class="substeps">
<label class="substep"><input type="checkbox" data-sub="p0-1" data-phase="p0"><span class="label">Peel <em>or</em> score the membrane on the bone side</span></label>
<label class="substep"><input type="checkbox" data-sub="p0-2" data-phase="p0"><span class="label">Sprinkle the salt evenly over both sides</span></label>
<label class="substep"><input type="checkbox" data-sub="p0-3" data-phase="p0"><span class="label">Rest uncovered on a rack in the fridge — no oil, no rub yet</span></label>
</div>
<div class="doneness"><b>You're ready when:</b> the surface looks dry and feels slightly tacky the next morning — that's the layer smoke and rub will cling to.</div>
<div class="callout gotcha"><svg class="icon"><use href="#ic-alert"/></svg><div><b>Watch out:</b> salt only tonight — the rub goes on tomorrow. Adding both now over-seasons the meat and the spices just dissolve overnight.</div></div>
<div class="callout tip"><svg class="icon"><use href="#ic-bulb"/></svg><div><b>Pro tip:</b> no wire rack? Rest the ribs on a bed of rough-chopped onion so air still reaches the underside. Salting up to 2 days ahead is fine — it just seasons a little deeper.</div></div>
<div class="callout clean"><svg class="icon"><use href="#ic-sparkle"/></svg><div><b>Easy cleanup:</b> line the sheet pan under the rack with foil. When you're done, the drips lift out with the foil — no crusty pan to scrub.</div></div>
<details class="why"><summary>Why this works</summary><p>Salt draws out a little moisture, then that brine gets reabsorbed, seasoning the meat all the way through and helping the proteins hold water during the long cook. The uncovered rest dries the surface so smoke and rub stick.</p></details>
</div>
<!-- P1 -->
<div class="phase" id="card-p1">
<svg class="phase-wm icon"><use href="#ic-smoke"/></svg>
<div class="phase-top">
<div style="display:flex;gap:14px">
<span class="phase-id">P1</span>
<div><h4>Low & smoky start</h4><p class="meta">~2.5 hrs · smoker 180–200°F / 82–93°C</p></div>
</div>
<button class="phase-check" data-phasecheck="p1" aria-pressed="false" title="Mark this step done"><svg class="icon"><use href="#ic-check"/></svg><span class="sr-only">Mark step done</span></button>
</div>
<p class="plain">Rub goes on now — <strong>bone side first, then flip and do the meat side last</strong>. Then a cold, low-and-slow start under humid smoke (that's the water pan's job — don't wet the ribs themselves) so the surface stays cool enough to keep taking it on.</p>
<div class="substeps">
<label class="substep"><input type="checkbox" data-sub="p1-1" data-phase="p1"><span class="label">Rub the <em>bone</em> side first, then flip and rub the meat side last <span class="crit-badge">order matters</span></span></label>
<label class="substep"><input type="checkbox" data-sub="p1-2" data-phase="p1"><span class="label">Set a foil-lined pan of <em>cold</em> water on the grate, between the fire and the ribs</span></label>
<label class="substep"><input type="checkbox" data-sub="p1-3" data-phase="p1" data-cookstart="1"><span class="label">Ribs on cold from the fridge, meat-side up <span class="crit-badge">starts your clock</span></span></label>
<div class="substep spritz-step">
<svg class="icon spritz-ic" aria-hidden="true"><use href="#ic-droplet"/></svg>
<span class="label">Spritz whenever the surface looks dry — ongoing, roughly hourly</span>
<button class="spritz-toggle" type="button" data-spritztoggle aria-pressed="false">🔔 nudge me hourly</button>
</div>
</div>
<!-- Live cook tracker — starts with the "ribs on" step above -->
<div class="predictor compact" id="predictor">
<!-- Before the ribs go on -->
<div class="p-idle" id="p-idle">
<div class="p-main">
<span class="eyebrow">Live cook tracker</span>
<div class="p-when" id="pred-when">Not started yet</div>
<div class="p-sub">Check “Ribs on the smoker” above (or tap the button) to start your cook clock — I’ll track where you are and ping you at each step, so you don’t have to watch it.</div>
</div>
<button class="btn btn-primary btn-sm" id="pred-start">Ribs are on 🔥</button>
</div>
<!-- Once cooking -->
<div class="p-live" id="p-live" hidden>
<div class="pl-grid">
<div class="pl-stat">
<span class="eyebrow">On the smoker</span>
<span class="pl-clock" id="pl-elapsed">0:00:00</span>
</div>
<div class="pl-stat">
<span class="eyebrow">Ready to eat ~</span>
<span class="pl-clock ember" id="pl-eat">—</span>
<span class="pl-togo" id="pl-togo"></span>
</div>
</div>
<div class="pl-now">
<div class="pl-now-head">
<span class="pl-phase" id="pl-phase">—</span>
<span class="pl-phase-time" id="pl-phase-time"></span>
</div>
<div class="pl-bar"><span id="pl-bar"></span></div>
<div class="pl-next" id="pl-next"></div>
</div>
<div class="p-coach" id="pred-coach" hidden></div>
<button class="p-reset" id="pred-reset" type="button">reset cook clock</button>
</div>
</div>
<div class="doneness"><b>You're ready when:</b> the surface has taken on color and a reddish smoke ring is starting — usually around the 2.5 hr mark.</div>
<div class="callout gotcha"><svg class="icon"><use href="#ic-alert"/></svg><div><b>Watch out:</b> don't reach for <em>hot</em> water to "help" the smoker. This phase works <em>because</em> it stays cool — cold (even iced) water keeps the surface in the smoke-taking zone longer. Warm it up early and the meat stops absorbing smoke.</div></div>
<div class="callout tip"><svg class="icon"><use href="#ic-bulb"/></svg><div><b>Pro tip:</b> season high and dry — dust the rub from ~12 in up <em>with dry hands</em> so it falls like light snow. Dry hands keep it from clumping, and the height gives even coverage where you can still just see the meat through it, not a thick cake. When you spritz, keep it gentle: enough to moisten, not so much you blast the rub off. And “dry” looks like pale, powdery patches in the rub — if the surface is still glossy, hold off.</div></div>
<div class="callout clean"><svg class="icon"><use href="#ic-sparkle"/></svg><div><b>Easy cleanup:</b> season over a rimmed sheet pan (or inside a big bowl) to catch the fallout — tip the extra back in the jar or into the trash, no counter to wipe. Foil-line the water pan too, so the greasy gunk lifts right out.</div></div>
<details class="why"><summary>Why this works</summary><p>Smoke only clings to a cool, moist surface, and meat stops taking it on once it climbs past ~140°F. A cold start with a cold-water pan stretches that window, so the ring and smoke flavor go deeper before the meat firms up. On a pellet smoker your smoke setting controls the smoke output — the water is there for humidity and to hold things cool, not to make more smoke. And the rub order? Bone side first means the meat side gets rubbed <em>last</em> — it lands on the grate freshly coated instead of ever sitting face-down on the board.</p></details>
<div class="phase-actions"><button class="btn btn-dark" data-rlabel="P1 · Low & smoky start" onclick="setReminder(150,this.dataset.rlabel,this)">Remind me in 2.5 hr</button></div>
</div>
<!-- P2 -->
<div class="phase" id="card-p2">
<svg class="phase-wm icon"><use href="#ic-droplet"/></svg>
<div class="phase-top">
<div style="display:flex;gap:14px">
<span class="phase-id">P2</span>
<div><h4>Build the bark</h4><p class="meta">~2 hrs · smoker ~235°F / 113°C</p></div>
</div>
<button class="phase-check" data-phasecheck="p2" aria-pressed="false" title="Mark this step done"><svg class="icon"><use href="#ic-check"/></svg><span class="sr-only">Mark step done</span></button>
</div>
<p class="plain">A light oil sets the bark. Keep the ribs <strong>meat-side up all the way until the wrap</strong> — the bones shield the meat from the heat below.</p>
<div class="substeps">
<label class="substep"><input type="checkbox" data-sub="p2-1" data-phase="p2"><span class="label">Mist lightly with neutral oil (avocado/canola) over the rub</span></label>
<label class="substep"><input type="checkbox" data-sub="p2-2" data-phase="p2"><span class="label">Rotate the rack 180° halfway through for even color — <em>keep meat-side up</em> <span class="crit-badge">meat stays up</span></span></label>
<div class="substep spritz-step">
<svg class="icon spritz-ic" aria-hidden="true"><use href="#ic-droplet"/></svg>
<span class="label">Keep spritzing whenever it looks dry — same hourly rhythm as P1</span>
<button class="spritz-toggle" type="button" data-spritztoggle aria-pressed="false">🔔 nudge me hourly</button>
</div>
</div>
<div class="doneness"><b>You're ready when:</b> the rub has set into a matte crust that doesn't wipe off, and the bark color is even across the rack.</div>
<div class="callout gotcha"><svg class="icon"><use href="#ic-alert"/></svg><div><b>Watch out:</b> keep the meat side <em>up</em> from here until the wrap. Turning it meat-side-down now puts your best meat right against the heat and dries it out — the bones are there to shield it. To even out hot spots, <em>rotate</em> the rack, don't flip it. (The wrap in P4 is the one deliberate exception — there the meat goes <em>down</em>, into a bed of butter and sugar.)</div></div>
<div class="callout tip"><svg class="icon"><use href="#ic-bulb"/></svg><div><b>Multiple racks?</b> Swap their shelf and side positions when you rotate — different spots in the smoker run at different temps. And from the wrap onward each rack is on its own clock: its own paper, its own probe checks. They rarely finish together.</div></div>
<div class="callout tip"><svg class="icon"><use href="#ic-bulb"/></svg><div><b>Pro tip:</b> oil is a light sheen, not a drench. One or two sprays a side is plenty — puddles just wash the rub off.</div></div>
<details class="why"><summary>Why this works</summary><p>A little oil dissolves the color and pepper compounds so the bark sets into one cohesive crust. Rotating evens out your smoker's hot spots without ever exposing the meat to direct heat.</p></details>
<div class="phase-actions"><button class="btn btn-dark" data-rlabel="P2 · Rotate the rack" onclick="setReminder(60,this.dataset.rlabel,this)">Remind me to rotate (1 hr)</button></div>
</div>
<!-- P3 -->
<div class="phase" id="card-p3">
<svg class="phase-wm icon"><use href="#ic-wind"/></svg>
<div class="phase-top">
<div style="display:flex;gap:14px">
<span class="phase-id">P3</span>
<div><h4>Dry the bark</h4><p class="meta">45–60 min · smoker ~250°F / 121°C</p></div>
</div>
<button class="phase-check" data-phasecheck="p3" aria-pressed="false" title="Mark this step done"><svg class="icon"><use href="#ic-check"/></svg><span class="sr-only">Mark step done</span></button>
</div>
<p class="plain">Now dry the surface so the bark sets firm before you wrap.</p>
<div class="substeps">
<label class="substep"><input type="checkbox" data-sub="p3-1" data-phase="p3"><span class="label">Raise the smoker to ~250°F / 121°C</span></label>
<label class="substep"><input type="checkbox" data-sub="p3-2" data-phase="p3"><span class="label">Remove the water pan <span class="crit-badge">don't skip</span></span></label>
<label class="substep"><input type="checkbox" data-sub="p3-3" data-phase="p3"><span class="label">No spritzing during this phase — let it dry</span></label>
</div>
<div class="doneness"><b>You're ready when:</b> the bark is set — touch it and it feels firm and dry, not sticky or wet.</div>
<div class="callout gotcha"><svg class="icon"><use href="#ic-alert"/></svg><div><b>Watch out:</b> this is the most-skipped step. Wrap while the bark is still wet and you'll steam it right back into mush.</div></div>
<div class="callout tip"><svg class="icon"><use href="#ic-bulb"/></svg><div><b>Pro tip:</b> bark-set test — drag a finger across the surface. If nothing rubs off, you're ready to wrap.</div></div>
<details class="why"><summary>Why this works</summary><p>Dropping the humidity lets the surface dry so the bark locks in place. Wrap while it's still wet and you'll steam it back to mush.</p></details>
<div class="phase-actions"><button class="btn btn-dark" data-rlabel="P3 · Dry the bark" onclick="setReminder(50,this.dataset.rlabel,this)">Remind me in 50 min</button></div>
</div>
<!-- P4 -->
<div class="phase" id="card-p4">
<svg class="phase-wm icon"><use href="#ic-package"/></svg>
<div class="phase-top">
<div style="display:flex;gap:14px">
<span class="phase-id">P4</span>
<div><h4>Wrap to tenderize</h4><p class="meta">1.5–2 hrs · smoker ~250°F / 121°C</p></div>
</div>
<button class="phase-check" data-phasecheck="p4" aria-pressed="false" title="Mark this step done"><svg class="icon"><use href="#ic-check"/></svg><span class="sr-only">Mark step done</span></button>
</div>
<p class="plain">Wrap tight to drive heat in and melt the collagen into silky gelatin. Build the bed on the paper where the <em>meat</em> side will land, roughly per rack: about <strong>2 tbsp brown sugar</strong>, <strong>2–3 thin pats of butter</strong>, and an optional drizzle of honey.</p>
<div class="substeps">
<label class="substep"><input type="checkbox" data-sub="p4-1" data-phase="p4"><span class="label">Lay 2 sheets of peach paper; make a rack-length line of brown sugar + butter (≈2 tbsp sugar, 2–3 pats butter per rack)</span></label>
<label class="substep"><input type="checkbox" data-sub="p4-2" data-phase="p4"><span class="label">Set the ribs meat-side <em>down</em> onto it <span class="crit-badge">easy to flip wrong</span></span></label>
<label class="substep"><input type="checkbox" data-sub="p4-3" data-phase="p4"><span class="label">Wrap tight, then back on the smoker <em>still meat-side down</em> so it braises in the butter & sugar <span class="crit-badge">stays meat-down</span></span></label>
</div>
<div class="doneness"><b>You're ready when:</b> a probe pushed <em>straight through the paper</em> into the meat between bones slides in with almost no resistance (around 200–205°F / 93–96°C internal). Don't unwrap to check — you'd dump the braise. Probe through; the <a href="#deflection-visualizer" style="color:var(--ember-text);font-weight:700">bend test</a> comes after you unwrap in the next step.</div>
<div class="callout gotcha"><svg class="icon"><use href="#ic-alert"/></svg><div><b>Watch out:</b> meat-side <em>down</em> onto the sugar and butter — and it <em>stays</em> meat-side down on the smoker so the meat braises in that buttery liquid. Easy to flip the wrong way.</div></div>
<div class="callout tip"><svg class="icon"><use href="#ic-bulb"/></svg><div><b>What good looks like:</b> a shallow, even bed the length of the rack — enough to melt into a glaze, not a pile that drowns the ribs. The bone side doesn't need the full treatment; a light sprinkle of brown sugar up there is plenty. Peach paper breathes and protects your bark (foil steams it soft) — a second sheet is cheap insurance against leaks.</div></div>
<div class="callout clean"><svg class="icon"><use href="#ic-sparkle"/></svg><div><b>Easy cleanup:</b> the butcher paper <em>is</em> your cleanup — no saucy pan to scrub. Ball it up and toss it when you unwrap.</div></div>
<details class="why"><summary>Why this works</summary><p>Wrapping traps steam and puts the paper in direct contact with the meat, driving heat in evenly. This is where tough collagen finally melts into silky gelatin — the difference between chewy and tender. Cooking it meat-side down lets the meat sit and braise in the melted butter and sugar, basting itself the whole time.</p></details>
<div class="phase-actions"><button class="btn btn-dark" data-rlabel="P4 · Wrapped & tenderizing" onclick="setReminder(105,this.dataset.rlabel,this)">Remind me in 1.75 hr</button></div>
</div>
<!-- P5 -->
<div class="phase" id="card-p5">
<svg class="phase-wm icon"><use href="#ic-thermometer"/></svg>
<div class="phase-top">
<div style="display:flex;gap:14px">
<span class="phase-id">P5</span>
<div><h4>Glaze & rest</h4><p class="meta">~40 min · quick glaze-set on the smoker, then off the heat</p></div>
</div>
<button class="phase-check" data-phasecheck="p5" aria-pressed="false" title="Mark this step done"><svg class="icon"><use href="#ic-check"/></svg><span class="sr-only">Mark step done</span></button>
</div>
<p class="plain">Unwrap and flip meat-side up, check doneness, glaze if you like, then rest — the step that actually keeps them juicy.</p>
<div class="substeps">
<label class="substep"><input type="checkbox" data-sub="p5-1" data-phase="p5"><span class="label">Unwrap, flip meat-side up, and confirm doneness with the bend test</span></label>
<label class="substep"><input type="checkbox" data-sub="p5-2" data-phase="p5"><span class="label">Optional glaze: brush <em>one thin coat</em>, then 8–10 min at ~275°F to set it</span></label>
<label class="substep"><input type="checkbox" data-sub="p5-3" data-phase="p5"><span class="label">Rest 15–30 min, loosely tented, then slice <span class="crit-badge">don't skip</span></span></label>
</div>
<div class="doneness"><b>You're ready when:</b> the rack has rested about <b>15–30 min</b> (it's settled to ~140°F / 60°C) — now the juices stay put instead of running out on the board.</div>
<div class="callout gotcha"><svg class="icon"><use href="#ic-alert"/></svg><div><b>Watch out:</b> don't slice them hot. Give a rack ~15–30 min tented — the single biggest thing that keeps ribs juicy. Skip it and great ribs dry out on the board.</div></div>
<div class="callout tip"><svg class="icon"><use href="#ic-bulb"/></svg><div><b>Glaze, done right:</b> warm your sauce so it brushes on <em>thin</em> — one light, even coat, not a gloopy layer that slides off. Set it with 8–10 min at ~275°F until it turns from wet to sticky (tacked up). To keep that glaze pristine while it rests, tent foil <em>loosely</em> so it doesn't touch the surface — press foil onto fresh glaze and it peels right back off.</div></div>
<div class="callout tip"><svg class="icon"><use href="#ic-bulb"/></svg><div><b>Pro tip:</b> guests running late? Hold the wrapped rack in a cooler (no ice) — it'll stay serving-hot for an hour or more.</div></div>
<div class="callout tip"><svg class="icon"><use href="#ic-bulb"/></svg><div><b>Slicing:</b> flip the rested rack bone-side up so you can see exactly where each bone runs, then cut between them in single smooth strokes — long knife, one pass per cut. Sawing shreds everything you just spent seven hours protecting.</div></div>
<div class="callout clean"><svg class="icon"><use href="#ic-sparkle"/></svg><div><b>Easy cleanup:</b> glaze straight from a small bowl with a brush, and if you flash-set on a pan, line it with foil first — set glaze is the worst thing to scrub off later.</div></div>
<details class="why"><summary>Why this works</summary><p>Slicing straight off the smoker lets thin, hot juices pour out. A short rest lets the melted gelatin thicken and hold that moisture inside the meat — and a thin glaze set with heat tacks onto the bark instead of sliding off.</p></details>
</div>
</div>
<!-- Timer panel -->
<div>
<div class="timer-panel">
<span class="eyebrow">Reminder timer</span>
<h4 id="active-timer-label">No timer running</h4>
<div class="timer-display"><span id="active-timer-display">00:00:00</span></div>
<p class="timer-note">A nudge, not a rule — check the ribs, not the clock.</p>
<p class="timer-note" id="wake-note" style="display:none">🔆 Screen stays awake while a cook or timer is running.</p>
<div class="timer-row">
<span>Adjust</span>
<div class="timer-adj">
<button onclick="adjustTimerMinutes(-10)">−10 min</button>
<button onclick="adjustTimerMinutes(10)">+10 min</button>
</div>
</div>
<div class="timer-cta">
<button class="btn btn-primary" id="timer-toggle-btn" onclick="toggleActiveTimer()">Start</button>
<button class="stop" onclick="resetActiveTimer()">Clear</button>
</div>
<div class="timer-test"><button onclick="playChime()">Test the chime sound</button></div>
</div>
</div>
</div>
</div>
</section>
<!-- ============ BEND TEST ============ -->
<section id="deflection-visualizer" class="bg-white">
<div class="wrap">
<div class="sec-head">
<span class="sec-ic"><svg class="icon"><use href="#ic-bend"/></svg></span>
<span class="eyebrow">The doneness test that matters</span>
<h2>The bend test</h2>
<p>This is how you actually know ribs are done. Lift the rack with tongs from one end — how it bends tells you everything. Drag the slider to train your eye.</p>
</div>
<div class="bend-wrap">
<div class="bend-stage">
<svg id="bend-svg" viewBox="0 0 320 200" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="A rack of ribs gripped by tongs at one end, sagging under its own weight — drag the slider to change how far it bends" style="max-width:96%;max-height:96%">
<defs>
<linearGradient id="rackMeat" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#54291D"/>
<stop offset=".4" stop-color="#8C4234"/>
<stop offset="1" stop-color="#6E3125"/>
</linearGradient>
</defs>
<!-- ghost line: where a stiff, underdone rack would sit -->
<path d="M 70,78 L 272,78" stroke="#D8C4A8" stroke-width="1.5" stroke-dasharray="4 7" opacity=".6"/>
<!-- tongs gripping the near end -->
<g stroke="#7A858C" stroke-width="4" stroke-linecap="round">
<path d="M 34,14 C 44,30 50,46 57,62"/>
<path d="M 24,24 C 36,38 46,52 55,66"/>
<path d="M 57,62 C 65,66 72,69 80,71"/>
<path d="M 55,66 C 63,76 71,84 80,89"/>
</g>
<circle cx="56" cy="64" r="3.5" fill="#4A555C"/>
<!-- the rack itself (outlines drawn by JS; piece B appears when it tears) -->
<path id="rack-a" fill="url(#rackMeat)" stroke="#2A130C" stroke-width="2.5" stroke-linejoin="round"/>
<path id="rack-b" fill="url(#rackMeat)" stroke="#2A130C" stroke-width="2.5" stroke-linejoin="round" style="display:none"/>
<g id="rack-bones" fill="#ECE8DF" stroke="#C9BFAE" stroke-width="1"></g>
<g id="rack-fibers" stroke="#B0563F" stroke-width="1.6" stroke-linecap="round" fill="none"></g>
<g id="rack-cracks" stroke="#F2875A" stroke-width="2" stroke-linecap="round" fill="none"></g>
<circle id="bend-drip" r="2.6" fill="#E8A15C"/>
</svg>
</div>
<div class="bend-scale">
<span>Underdone<br>(stiff)</span>