-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathworkbench.html
More file actions
2863 lines (2811 loc) Β· 214 KB
/
Copy pathworkbench.html
File metadata and controls
2863 lines (2811 loc) Β· 214 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">
<title>Flavormancer β taste & aroma from chemical structure</title>
<meta name="description" content="Flavormancer predicts taste & aroma from a molecule's chemical structure β on-prem flavor prediction for food, beverage, and fragrance R&D.">
<meta name="author" content="Echelon Technology Solutions">
<meta name="application-name" content="Flavormancer">
<meta property="og:type" content="website">
<meta property="og:site_name" content="Flavormancer">
<meta property="og:url" content="https://flavormancer.echelonts.net/">
<meta property="og:locale" content="en_US">
<meta property="og:title" content="Flavormancer β taste & aroma from chemical structure, before you pour">
<meta property="og:description" content="Taste & aroma prediction from chemical structure β before you pour. On-prem flavor prediction, behind your own firewall.">
<meta property="og:image" content="https://flavormancer.echelonts.net/static/social.png">
<meta property="og:image:secure_url" content="https://flavormancer.echelonts.net/static/social.png">
<meta property="og:image:type" content="image/png">
<meta property="og:image:width" content="1280">
<meta property="og:image:height" content="640">
<meta property="og:image:alt" content="Flavormancer β taste and aroma prediction from chemical structure">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Flavormancer β taste & aroma from chemical structure, before you pour">
<meta name="twitter:description" content="Taste & aroma prediction from chemical structure β before you pour. On-prem flavor prediction, behind your own firewall.">
<meta name="twitter:image" content="https://flavormancer.echelonts.net/static/social.png">
<meta name="twitter:image:alt" content="Flavormancer β taste and aroma prediction from chemical structure">
<meta name="theme-color" content="#2BC4C4">
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
<link rel="icon" type="image/png" sizes="32x32" href="/static/favicon.png">
<link rel="apple-touch-icon" href="/static/apple-touch-icon.png">
<style>
@font-face{font-family:'Cinzel Decorative';src:url('/static/wordmark.ttf') format('truetype');font-weight:700;font-display:swap}
@font-face{font-family:'Grenze Gotisch';src:url('/static/headerfont.ttf') format('truetype');font-weight:700;font-display:swap}
:root{
--surface:#0F1319; --panel:#181D24; --line:#2A323C; --ink:#E7EDEA;
--muted:#94A2A9; --accent:#2BC4C4; --accent-soft:#2BC4C42A;
--brand-1:#8A6BE0; --brand-2:#2BC4C4; --brand-accent:#E0913C; --cream:#D9AB74;
--brand-grad:linear-gradient(100deg,#8A6BE0 0%,#4E84C8 45%,#2BC4C4 100%);
--sweet:#E8A94A; --bitter:#A88AE0; --umami:#E0805E; --sour:#BFD24E; --salty:#63A6E0; --aroma:#3FC4AA;
--struct-bg:#F5F7F5; /* light 'paper' for molecule drawings (RDKit draws dark structures) */
--ui:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
--mono:ui-monospace,"SF Mono",Menlo,Consolas,monospace;
}
*{box-sizing:border-box}
html{scroll-behavior:smooth}
/* never let the page scroll/zoom horizontally β inner scroll containers (e.g. the enrichment
table) keep their own overflow:auto, so this only kills phantom document-level overflow that
was zooming mobile out to ~5/6 width. */
html,body{overflow-x:hidden;max-width:100%}
/* ambient alchemical aura β faint colored light pooling in the viewport corners (fixed, so it
stays put as you scroll), layered over the base surface color */
body{margin:0;color:var(--ink);font-family:var(--ui);
font-size:15px;line-height:1.5;-webkit-font-smoothing:antialiased;
background:
radial-gradient(58% 48% at 10% 4%, rgba(138,107,224,.13), transparent 60%),
radial-gradient(52% 42% at 92% 98%, rgba(43,196,196,.11), transparent 62%),
radial-gradient(42% 38% at 97% 6%, rgba(217,171,116,.07), transparent 60%),
radial-gradient(46% 40% at 3% 94%, rgba(78,132,200,.07), transparent 62%),
var(--surface);
background-attachment:fixed}
::selection{background:rgba(43,196,196,.30);color:#fff}
*::-webkit-scrollbar{width:11px;height:11px}
*::-webkit-scrollbar-thumb{background:#2A343E;border-radius:7px;border:2px solid var(--surface)}
*::-webkit-scrollbar-thumb:hover{background:#3A4653}
*::-webkit-scrollbar-track{background:transparent}
a{color:var(--brand-2)}
:focus-visible{outline:2px solid var(--brand-2);outline-offset:2px;border-radius:4px}
header{position:relative;padding:34px 28px 26px;border-bottom:1px solid var(--line);display:flex;
flex-direction:column;align-items:center;gap:6px;text-align:center;
background:radial-gradient(130% 150% at 50% -30%, #1D2A38 0%, var(--surface) 62%)}
header .logo{width:min(344px,72vw);height:auto;margin-bottom:18px;
filter:drop-shadow(0 8px 38px rgba(120,150,230,.34))}
header .brand{display:flex;flex-direction:column;align-items:center;gap:8px}
header h1{margin:0;font-family:'Grenze Gotisch',"Unifrakturmaguntia",Georgia,fantasy;
font-size:56px;font-weight:700;letter-spacing:.015em;line-height:1;
background:linear-gradient(100deg,#8A6BE0 0%,#4E84C8 30%,#2BC4C4 50%,#4E84C8 70%,#8A6BE0 100%);
background-size:220% auto;-webkit-background-clip:text;background-clip:text;color:transparent;
filter:drop-shadow(0 2px 18px rgba(110,140,220,.38));animation:wordShimmer 9s linear infinite}
@keyframes wordShimmer{to{background-position:220% center}}
@media(prefers-reduced-motion:reduce){header h1{animation:none}.loader-ring,.loader-flask .bub,.loader-flask .wisp,header::before,header::after{animation:none}}
header .sub{font-family:'Cinzel Decorative',"Luminari",Georgia,serif;font-size:15px;
font-weight:700;text-transform:uppercase;letter-spacing:.16em;color:var(--cream);position:relative}
header::before{content:'';position:absolute;top:-30px;left:50%;width:540px;height:340px;
transform:translateX(-50%);pointer-events:none;z-index:0;
background:radial-gradient(closest-side,rgba(43,196,196,.11),transparent 72%);animation:heroPulse 6.5s ease-in-out infinite}
header>*{position:relative;z-index:1}
@keyframes heroPulse{0%,100%{opacity:.5}50%{opacity:1}}
/* alchemical sparkle: tiny twinkling motes scattered behind the emblem/wordmark (z-index:0,
under the z-index:1 content, so they never sit on top of the text) */
header::after{content:'';position:absolute;inset:0;pointer-events:none;z-index:0;
background:
radial-gradient(circle 1.5px at 16% 26%, rgba(217,171,116,.95), transparent 60%),
radial-gradient(circle 1.2px at 84% 18%, rgba(43,196,196,.9), transparent 60%),
radial-gradient(circle 1px at 25% 62%, rgba(255,255,255,.8), transparent 60%),
radial-gradient(circle 1.3px at 90% 58%, rgba(138,107,224,.85), transparent 60%),
radial-gradient(circle 1px at 9% 46%, rgba(43,196,196,.75), transparent 60%),
radial-gradient(circle 1.1px at 78% 74%, rgba(217,171,116,.8), transparent 60%),
radial-gradient(circle 1px at 40% 15%, rgba(255,255,255,.65), transparent 60%),
radial-gradient(circle 1.2px at 62% 70%, rgba(138,107,224,.7), transparent 60%);
animation:twinkle 5s ease-in-out infinite}
@keyframes twinkle{0%,100%{opacity:.3}50%{opacity:.9}}
/* full-width and centered, capped for readability on large monitors; fluid side padding so
phones/tablets/laptops all fill the screen (no dead 'column' margins under ~1500px). */
main{width:100%;max-width:1500px;margin:0 auto;padding:28px clamp(16px,4vw,40px) 64px}
.bar{display:flex;gap:10px;align-items:stretch}
.bar input{flex:1;font-family:var(--mono);font-size:15.5px;padding:15px 17px;
border:1px solid var(--line);border-radius:11px;background:var(--panel);color:var(--ink);
transition:box-shadow .22s ease,border-color .22s ease}
.bar input:focus{outline:none;border-color:var(--brand-2);
box-shadow:0 0 0 3px var(--accent-soft),0 0 30px rgba(43,196,196,.22)}
.bar button{font-family:var(--ui);font-weight:650;font-size:15px;padding:0 26px;
border:none;border-radius:11px;background:var(--brand-grad);color:#fff;cursor:pointer;
box-shadow:0 4px 18px rgba(78,132,200,.34);transition:transform .14s ease,box-shadow .2s ease}
.bar button:hover:not(:disabled){transform:translateY(-1px);box-shadow:0 6px 24px rgba(78,132,200,.45)}
.bar button:disabled{opacity:.5;cursor:default}
.hint{font-family:var(--mono);font-size:12px;color:var(--muted);margin:8px 2px 0}
.tour{display:flex;flex-wrap:wrap;gap:7px;align-items:center;margin:12px 2px 0}
.tour-label{font-size:12px;color:var(--cream)}
.tour-chip{font:inherit;font-size:12.5px;padding:4px 12px;border:1px solid var(--line);border-radius:20px;
background:var(--panel);color:var(--ink);cursor:pointer;transition:all .14s}
.tour-chip:hover{border-color:transparent;background:var(--brand-grad);color:#08121A;transform:translateY(-1px)}
.grid{display:grid;grid-template-columns:1fr 1fr;gap:18px;margin-top:24px}
.grid>.card{min-width:0} /* let cards shrink to the track so their content wraps, not overflow */
@media(max-width:760px){.grid{grid-template-columns:1fr}}
.card{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:20px 22px}
.card h2{margin:0 0 4px;font-size:12px;font-weight:650;text-transform:uppercase;
letter-spacing:.08em;color:var(--muted)}
.smiles{font-family:var(--mono);font-size:13px;color:var(--muted);margin:0 0 4px;
word-break:break-all}
.cname{font-size:16px;font-weight:650;color:var(--ink);margin:0 0 4px}
.iupac{font-family:var(--mono);font-size:11px;color:var(--muted);margin:0 0 14px;word-break:break-all}
.meter{margin:13px 0}
.meters-note{font-size:11.5px;color:var(--muted);line-height:1.5;margin:10px 2px 2px;border-top:1px solid #EEF0EC;padding-top:9px}
.meter .row{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:5px}
.meter-help{font-size:11px;color:var(--muted);margin-top:4px;line-height:1.4}
.flavor-tags{display:flex;flex-wrap:wrap;gap:5px;align-items:center;margin:12px 0 2px}
.flavor-tags .ft-label{font-size:11px;color:var(--muted);margin-right:2px}
.ftag{font-size:11px;padding:2px 9px;border-radius:20px;text-transform:capitalize;font-weight:600}
.ftag-flavor{background:var(--brand-grad);color:#08121A}
.ftag-taste{color:#08121A}
.ftag-aroma{background:transparent;border:1px solid;font-weight:600}
.references{display:flex;flex-wrap:wrap;gap:10px;align-items:center;margin:8px 0 2px}
.references .ft-label{font-size:11px;color:var(--muted)}
.references a{font-size:11.5px;color:var(--brand-2);text-decoration:none;border-bottom:1px dotted var(--brand-2)}
.references a:hover{color:var(--ink);border-bottom-color:var(--ink)}
.ref-spectra{font-size:10px;color:var(--muted);background:var(--panel);border:1px solid var(--line);border-radius:9px;padding:1px 7px;margin-left:2px}
.meter .name{font-weight:600;font-size:14px;text-transform:capitalize}
.meter .val{font-family:var(--mono);font-size:13px;color:var(--muted)}
.meter .au,.ameter .au{font-size:11px;color:var(--muted);opacity:.85}
.meter .tag,.ameter .tag{font-family:var(--mono);font-size:10px;padding:2px 7px;border-radius:20px;
margin-left:8px;vertical-align:middle;letter-spacing:.04em}
.tag.measured{background:var(--accent-soft);color:var(--accent)}
.tag.predicted{background:#EDEFEC;color:var(--muted)}
.tag.computed{background:rgba(99,166,224,.16);color:#63A6E0}
.tag.estimate{background:rgba(232,169,74,.16);color:var(--sweet)}
.tag.qualitative{background:#242C35;color:var(--muted)}
.track{height:9px;background:#EDEFEC;border-radius:6px;overflow:hidden}
.fill{height:100%;border-radius:6px;width:0;transition:width .5s ease}
.chips{display:flex;flex-wrap:wrap;gap:8px;margin-top:6px}
.chip{font-family:var(--mono);font-size:12px;padding:5px 11px;border-radius:20px;
border:1px solid var(--line);background:var(--surface)}
.tox-code{font-size:9.5px;opacity:.62;letter-spacing:.02em}
.chip[title]{cursor:help}
/* mouthfeel target chips: dotted amber edge, so the sensation modality reads apart from the
aroma-note chips beside it (cooling/pungent legitimately appear in both groups) */
.chip-mouth{border-style:dotted;border-color:var(--accent);color:var(--accent)}
.chip.on{color:#fff;border-color:transparent}
/* scroll the qualifying set (every match above the similarity floor) instead of a fixed few;
~5 rows tall, then scroll. A subtle fade at the bottom hints there's more below. */
.nb-list{max-height:430px;overflow-y:auto;overflow-x:hidden;margin:0 -4px;padding:0 4px}
.nb-list::-webkit-scrollbar{width:8px}
.nb-list::-webkit-scrollbar-thumb{background:var(--line);border-radius:4px}
.nb-count{font-family:var(--mono);font-size:12px;font-weight:400;color:var(--muted);margin-left:6px}
.neighbor{display:flex;align-items:center;gap:12px;
padding:11px 0;border-bottom:1px solid var(--line)}
.neighbor:last-child{border-bottom:none}
.neighbor{cursor:pointer}
.neighbor:hover{background:var(--accent-soft)}
.neighbor .nb-struct{flex:0 0 auto;width:64px;height:48px;background:#fff;border:1px solid var(--line);border-radius:6px}
.neighbor .nb-struct svg{width:64px;height:48px}
.neighbor .nb-info{flex:1;min-width:0}
.neighbor .nb-name{font-weight:600;font-size:13px;text-transform:capitalize;color:var(--ink)}
.neighbor .nb-iupac{font-family:var(--mono);font-size:10px;color:var(--muted);word-break:break-all;margin:1px 0}
.nb-aroma{display:flex;flex-wrap:wrap;gap:4px;margin-top:3px}
.nb-aroma .atag{font-size:10px;padding:1px 7px;border-radius:20px;background:transparent;color:var(--aroma);border:1px solid;text-transform:capitalize}
.kt{display:flex;flex-wrap:wrap;gap:4px;margin-top:3px}
.rx-from{font-size:10px;color:var(--muted);line-height:1.3;margin-top:3px}
.kt .tchip{font-size:10px;font-weight:600;padding:1px 8px;border-radius:20px;color:#10140F;text-transform:capitalize}
.kt .tchip.pred{background:transparent!important;border:1px solid;font-weight:500}
.kt .muted{color:var(--muted);font-size:11px}
.browse{margin:22px 0 4px;border:1px solid #2C6E74;border-radius:12px;padding:15px 18px;background:linear-gradient(180deg,#15211F,#181D24)}
.browse-head{font-size:12px;font-weight:650;text-transform:uppercase;letter-spacing:.04em;color:var(--brand-2);margin:0 0 12px;display:flex;align-items:center;justify-content:space-between;gap:8px}
.browse-toggle{display:inline-flex}
.browse-toggle .st-tab{padding:3px 11px}
.browse-cats{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:14px}
.cat{font:inherit;font-size:12px;font-weight:600;padding:5px 12px;border-radius:20px;border:1px solid var(--line);background:#fff;color:var(--ink);cursor:pointer;text-transform:capitalize}
.cat:hover{border-color:var(--aroma)}
.cat.on{background:var(--aroma);color:#fff;border-color:var(--aroma)}
.cat.taste.on{background:var(--sweet);border-color:var(--sweet)}
.browse-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:10px}
.bcell{position:relative;border:1px solid var(--line);border-radius:8px;padding:8px;cursor:pointer;background:#fff;text-align:center;transition:border-color .15s}
.mini-3d{position:absolute;top:6px;right:6px;z-index:2;font:inherit;font-size:10px;font-weight:600;padding:1px 6px;border:1px solid var(--line);border-radius:5px;background:#fff;color:var(--muted);cursor:pointer}
.mini-3d:hover{border-color:var(--aroma);color:var(--aroma)}
.bcell .bstruct{position:relative}
.bcell .bstruct.is3d{height:130px}
.nb-structwrap{position:relative;flex:0 0 auto}
.nb-3d{top:2px;right:2px;padding:0 5px}
.neighbor .nb-struct{position:relative}
.neighbor .nb-struct.is3d{width:108px;height:88px}
.bcell:hover{border-color:var(--aroma)}
.bcell .bstruct{height:68px}.bcell .bstruct svg{max-width:100%;height:68px}
.bcell .bname{font-size:12px;font-weight:600;margin-top:5px;line-height:1.25;text-transform:capitalize;cursor:pointer}
.bcell .bname:hover{color:var(--aroma)}
.map-section{margin:22px 0 8px}
.map-head{font-size:12px;font-weight:650;text-transform:uppercase;letter-spacing:.04em;color:var(--muted);cursor:pointer}
.map-head .mapsub{font-weight:600;text-transform:none;letter-spacing:0;font-size:12px;color:var(--muted);opacity:.8}
.map-legend{display:flex;flex-wrap:wrap;gap:8px;margin:10px 0 6px;font-size:12px}
.map-legend span{display:inline-flex;align-items:center;gap:5px;cursor:pointer;padding:2px 8px;border-radius:20px;border:1px solid transparent;transition:all .12s}
.map-legend span:hover{border-color:var(--line)}
.map-legend span.on{border-color:var(--brand-2);background:rgba(43,196,196,.10);color:var(--ink)}
.map-legend span.leg-zero{opacity:.42;cursor:default}
.map-legend span.leg-zero:hover{border-color:transparent}
.map-legend i{width:10px;height:10px;border-radius:50%;display:inline-block}
.map-controls{display:flex;flex-wrap:wrap;align-items:center;gap:12px;margin:10px 0 8px}
.map-toggle3d{display:inline-flex}
.map-toggle3d .st-tab{padding:3px 12px}
.map-reset{font:inherit;font-size:12px;padding:3px 10px;border:1px solid var(--line);border-radius:6px;background:#fff;color:var(--muted);cursor:pointer;margin-left:auto}
.map-reset:hover{border-color:var(--aroma)}
.map-axis-key{font-size:11.5px;color:var(--cream);margin-top:9px;display:flex;flex-wrap:wrap;gap:12px;align-items:center}
.map-axis-key .ax{display:inline-flex;align-items:center;gap:5px}
.map-axis-key .ax i{width:16px;height:3px;border-radius:2px;display:inline-block}
.map-axis-key .ax-abstract{color:var(--muted)}
.map-key{font-size:12px;color:var(--muted);line-height:1.55;margin-top:8px}
.map-method{margin-top:10px;border:1px solid var(--line);border-radius:9px;background:var(--panel)}
.map-method>summary{cursor:pointer;padding:9px 13px;font-size:12px;font-weight:600;color:var(--brand-2);list-style:none}
.map-method>summary::-webkit-details-marker{display:none}
.map-method>summary::before{content:'βΈ ';color:var(--muted)}
.map-method[open]>summary::before{content:'βΎ '}
.map-method-body{padding:2px 15px 12px;font-size:12px;color:var(--muted);line-height:1.6}
.map-method-body p{margin:8px 0}.map-method-body b{color:var(--ink)}
.map-key b{color:var(--ink)}
.map-canvas-wrap{position:relative;width:100%;overflow-x:auto}
#flavorMap{max-width:100%;border:1px solid var(--line);border-radius:8px;background:#fff;cursor:crosshair}
/* reusable branded loading overlay for the flavor map + enrichment grid (first-open feedback) */
.load-overlay{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;
gap:12px;background:var(--surface);border-radius:8px;z-index:5;text-align:center;padding:20px}
.load-overlay.done{opacity:0;pointer-events:none;transition:opacity .35s ease}
.load-ring{width:38px;height:38px;border-radius:50%;border:3px solid var(--line);
border-top-color:var(--brand-2);border-right-color:var(--brand-1);animation:loadspin .8s linear infinite}
@keyframes loadspin{to{transform:rotate(360deg)}}
.load-lbl{font-family:'Cinzel Decorative',Georgia,serif;font-size:13.5px;letter-spacing:.04em;color:var(--cream)}
.load-sub{font-family:var(--mono);font-size:11.5px;color:var(--muted);margin-top:-4px}
.et-loading{display:flex;align-items:center;justify-content:center;gap:12px}
.et-loading .load-ring{width:22px;height:22px;border-width:2.5px}
@media(prefers-reduced-motion:reduce){.load-ring{animation:none}}
.map-tip{position:absolute;pointer-events:none;background:#1c2620;color:#fff;font-size:11px;padding:4px 8px;border-radius:6px;white-space:nowrap;transform:translate(-50%,-150%);box-shadow:0 4px 14px rgba(0,0,0,.4)}
.map-tip .tip-coord{font-family:var(--mono);font-size:10px;color:#9FE8D8}
.neighbor .s{font-family:var(--mono);font-size:12px;color:var(--muted);word-break:break-all}
.neighbor .sim{font-family:var(--mono);font-size:12px;color:var(--accent);white-space:nowrap}
.neighbor .kt{font-family:var(--mono);font-size:11px;color:var(--muted)}
.empty{color:var(--muted);font-size:14px;padding:8px 0}
.err{background:#FBEDE9;border:1px solid #E7C4B8;color:#8A3A22;border-radius:9px;
padding:12px 15px;margin-top:18px;font-size:14px}
.note{font-size:12.5px;color:var(--muted);margin-top:18px;line-height:1.55}
.card-sub{font-size:12.5px;color:var(--muted);margin:0 0 14px;line-height:1.5}
.aroma-card{margin-top:18px;border-color:#BFD8D0;background:linear-gradient(180deg,#F2F8F6,#FFFFFF)}
.aroma-card h2{color:var(--aroma)}
.aroma-lead{font-size:15px;font-weight:600;color:var(--ink);margin-bottom:6px}
.aroma-lead b{color:var(--aroma)}
.aroma-note{font-size:13px;color:var(--muted);margin:0;line-height:1.55;max-width:760px}
.aroma-note b{color:var(--ink)}
.aroma-prev-tag{font-size:11px;font-weight:650;text-transform:uppercase;letter-spacing:.05em;color:var(--aroma);margin:14px 0 10px}
.aroma-doc-tag{font-size:11px;font-weight:650;text-transform:uppercase;letter-spacing:.05em;color:var(--muted);margin:0 0 8px}
.aroma-doc{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:4px}
.odor-note{background:#F1F4EF;border:1px solid #DDE3D8;border-radius:6px;padding:4px 9px;font-size:12.5px;line-height:1.35}
.odor-thr{font-size:13px;line-height:1.4}
.odor-thr b{font-family:var(--mono);color:var(--aroma)}
.odor-thr span{color:var(--muted);font-size:12px}
.ameter{margin:8px 0}
.ameter.dim{opacity:.4}.ameter.dim:hover{opacity:.75}
.arow{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:4px}
.aname{font-weight:600;font-size:13px;text-transform:capitalize}
.aval{font-family:var(--mono);font-size:12px;color:var(--muted)}
.atrack{height:8px;background:#EDEFEC;border-radius:5px;overflow:hidden}
.afill{height:100%;background:var(--aroma);border-radius:5px;transition:width .5s ease}
.kv{display:grid;grid-template-columns:auto 1fr;gap:8px 16px;margin-bottom:10px}
.kv .k{font-weight:600;font-size:13px;max-width:230px}
.kv .k-why{font-weight:400;font-size:10.5px;color:var(--muted);line-height:1.35;margin-top:2px}
.kv .v{font-family:var(--mono);font-size:13px;color:var(--muted)}
.bsub{font-size:11px;font-weight:650;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);margin:14px 0 6px}
.fnote{font-size:13px;color:var(--ink);line-height:1.5}
.chiral-note{font-size:12px;color:var(--cream);background:rgba(217,171,116,.09);border:1px solid rgba(217,171,116,.32);border-radius:8px;padding:9px 12px;margin-top:13px;line-height:1.5}
.chiral-note b{color:var(--cream)}
.gate-tag{display:block;margin-top:6px;font-size:11px;color:var(--muted);font-style:italic;border-left:2px solid var(--brand-2);padding-left:8px}
.stereo-btn{display:inline-block;margin-top:9px;font:inherit;font-size:12px;font-weight:600;padding:5px 12px;border:1px solid rgba(217,171,116,.5);border-radius:7px;background:rgba(217,171,116,.12);color:var(--cream);cursor:pointer}
.stereo-btn:hover{background:rgba(217,171,116,.22)}
.stereo-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:11px;margin-top:12px}
.stereo-cell{position:relative;border:1px solid var(--line);border-radius:9px;padding:9px;background:var(--panel);transition:border-color .12s}
.stereo-cell:hover{border-color:var(--cream)}
.stereo-struct{position:relative;height:78px;background:var(--struct-bg);border-radius:6px;text-align:center}.stereo-struct svg{height:78px;max-width:100%}
.stereo-struct.is3d{height:130px}
.stereo-open{margin-top:8px;width:100%;font:inherit;font-size:11px;font-weight:600;padding:4px;border:1px solid var(--line);border-radius:6px;background:transparent;color:var(--brand-2);cursor:pointer}
.stereo-open:hover{border-color:var(--brand-2);background:rgba(43,196,196,.08)}
.stereo-label[data-smi],.stereo-name[data-smi]{cursor:pointer}
.stereo-label{font-family:var(--mono);font-weight:700;font-size:13px;margin-top:7px;color:var(--ink)}
.stereo-badge{font-size:8px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;padding:1px 6px;border-radius:9px;background:var(--aroma);color:#08121A;margin-left:6px;vertical-align:middle}
.stereo-name{font-size:11px;color:var(--muted);line-height:1.35;margin-top:2px}
.stereo-doc{font-size:11px;color:var(--cream);margin-top:4px;line-height:1.4}
.structure{text-align:center;margin:0 0 14px;background:#fff;border:1px solid var(--line);border-radius:8px;padding:6px}
.structure svg{max-width:100%;height:auto}
.structure:empty{display:none}
.struct-toggle{display:flex;gap:0;justify-content:center;margin:0 0 8px}
.st-tab{font:inherit;font-size:12px;font-weight:600;padding:4px 14px;border:1px solid var(--line);background:#fff;color:var(--muted);cursor:pointer}
.st-tab:first-child{border-radius:6px 0 0 6px;border-right:none}
.st-tab:last-child{border-radius:0 6px 6px 0}
.st-tab.on{background:var(--aroma);color:#fff;border-color:var(--aroma)}
.structure3d{position:relative;width:100%;height:280px;margin:0 0 14px;background:#fff;border:1px solid var(--line);border-radius:8px;overflow:hidden}
.s3-empty{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:var(--muted);font-size:13px}
.domain-banner{background:#FFF6E6;border:1px solid #E8D38A;color:#7A5A12;border-radius:8px;padding:10px 13px;font-size:13px;margin-bottom:14px;line-height:1.5}
.hint code{font-family:var(--mono);background:var(--accent-soft);padding:1px 5px;border-radius:4px;color:var(--accent)}
.toggle{font-size:12px;font-weight:650;text-transform:uppercase;letter-spacing:.04em;color:var(--brand-2);margin:22px 0 0;display:flex;align-items:center;gap:7px;cursor:pointer;padding:2px}
.toggle:hover{color:var(--ink)}
.toggle input[type=checkbox]{display:none} /* caret is the control, no raw checkbox */
#mixPanel{margin-top:12px;background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:18px 20px}
.mix-explain{font-size:12.5px;color:var(--muted);line-height:1.55;margin:0 0 12px;max-width:840px}
.mix-explain b{color:var(--ink)}
.mix-examples{font-size:12px;color:var(--muted);margin:0 0 13px;display:flex;flex-wrap:wrap;gap:7px;align-items:center}
.mix-ex{font:inherit;font-size:11.5px;padding:3px 10px;border:1px solid var(--line);border-radius:16px;background:transparent;color:var(--brand-2);cursor:pointer}
.mix-ex:hover{border-color:var(--brand-2)}
.mix-row{display:flex;gap:8px;margin-bottom:8px}
.mix-row input{flex:1;font-family:var(--mono);font-size:14px;padding:9px 12px;border:1px solid var(--line);border-radius:8px;background:var(--surface)}
.mix-row .rm{border:1px solid var(--line);background:var(--surface);border-radius:8px;padding:0 13px;cursor:pointer;color:var(--muted)}
.mix-add{font-family:var(--ui);font-size:13px;padding:7px 12px;border:1px dashed var(--line);background:var(--surface);border-radius:8px;cursor:pointer;margin:2px 0 12px}
.mix-proc{font-family:var(--mono);font-size:12px;color:var(--muted);margin:4px 0 14px;display:flex;gap:16px;flex-wrap:wrap;align-items:center}
.mix-btn{font-family:var(--ui);font-weight:600;font-size:14px;padding:9px 18px;border:none;border-radius:8px;background:var(--accent);color:#fff;cursor:pointer}
.mix-btn:disabled{opacity:.5}
#mixResults{margin-top:14px}
/* how it works */
.how-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin:4px 0 4px}
@media(max-width:820px){.how-grid{grid-template-columns:1fr 1fr}}
@media(max-width:560px){.how-grid{grid-template-columns:1fr}}
.how-card{border:1px solid var(--line);border-radius:10px;padding:12px 14px;background:var(--surface)}
.how-card h4{font-family:'Cinzel',Georgia,serif;font-size:13px;margin:0 0 7px;color:var(--brand-2)}
.how-card p{font-size:12.5px;line-height:1.55;color:var(--muted);margin:0}
.how-card p b{color:var(--ink)}
.how-foot{font-size:11.5px;color:var(--muted);margin:11px 2px 0;line-height:1.5}
.how-foot b{color:var(--cream)}
.how-foot a{color:var(--cream);text-decoration:none;border-bottom:1px dotted var(--cream)}
.how-foot a:hover{color:var(--ink);border-bottom-color:var(--ink)}
/* formulation studio */
.form-ex{font:inherit;font-size:11.5px;padding:3px 10px;border:1px solid var(--line);border-radius:16px;background:transparent;color:var(--brand-2);cursor:pointer}
.form-ex:hover{border-color:var(--brand-2)}
.form-cols{display:flex;gap:8px;font-family:var(--mono);font-size:10.5px;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);margin:2px 2px 5px}
.form-cols .form-col-h:first-child{flex:1}
.form-col-ppm{width:96px;margin-right:34px}
.form-row{display:flex;gap:8px;margin-bottom:8px}
.form-row input.f-name{flex:1;font-family:var(--mono);font-size:14px;padding:9px 12px;border:1px solid var(--line);border-radius:8px;background:var(--surface);color:var(--ink)}
.form-row input.f-ppm{width:96px;font-family:var(--mono);font-size:14px;padding:9px 10px;border:1px solid var(--line);border-radius:8px;background:var(--surface);color:var(--ink);text-align:right}
.form-row .rm{border:1px solid var(--line);background:var(--surface);border-radius:8px;padding:0 13px;cursor:pointer;color:var(--muted)}
.form-target{margin:6px 0 14px}
.form-target-head{font-family:var(--mono);font-size:11.5px;color:var(--brand-2);margin:0 0 8px}
.form-chips{display:flex;flex-wrap:wrap;gap:6px;max-height:112px;overflow-y:auto;padding:2px;flex:1}
.form-chips .chip{cursor:pointer;text-transform:capitalize;border-color:var(--line)}
.form-chips .chip.on{background:var(--brand-grad);color:#08121A;border-color:transparent;font-weight:650}
.form-chips .chip-flavor.on{background:var(--cream);color:#08121A}
.form-tgt-group{display:flex;gap:10px;align-items:flex-start;margin-bottom:8px}
.form-tgt-label{font-family:var(--mono);font-size:11px;color:var(--cream);white-space:nowrap;padding-top:6px;min-width:70px;display:inline-flex;align-items:center;gap:4px}
.form-tgt-label .ic{width:13px;height:13px;color:var(--cream)}
.form-actions{display:flex;gap:10px;flex-wrap:wrap}
#formDesign{background:var(--brand-grad);color:#08121A}
.recipe-note{font-size:12px;color:var(--cream);background:rgba(43,196,196,.08);border-left:2px solid var(--brand-2);border-radius:0 8px 8px 0;padding:8px 11px;margin:2px 0 10px;line-height:1.5}
.recipe-strip{display:flex;flex-wrap:wrap;gap:8px;margin:2px 0 12px}
.recipe-ing{border:1px solid var(--line);border-radius:9px;padding:7px 11px;background:var(--surface);min-width:150px}
.recipe-ing .ri-name{font-size:12.5px;font-weight:600;color:var(--ink)}
.recipe-ing .ri-meta{font-family:var(--mono);font-size:10.5px;color:var(--muted);margin-top:2px}
.recipe-ing .ri-carries{font-size:10.5px;color:var(--brand-2);margin-top:2px;text-transform:capitalize}
#formResults{margin-top:16px}
.fprof-basis{font-family:var(--mono);font-size:11.5px;color:var(--muted);margin:0 0 12px;padding:8px 11px;border-left:2px solid var(--brand-2);background:var(--accent-soft);border-radius:0 8px 8px 0;line-height:1.5}
.fprof-row{display:grid;grid-template-columns:118px 1fr 46px;gap:10px;align-items:center;margin-bottom:7px}
.fprof-note{font-size:13px;font-weight:600;text-transform:capitalize;text-align:right;color:var(--ink)}
.fprof-bar{position:relative}
.fprof-track{height:13px;background:#242C35;border-radius:7px;overflow:hidden}
.fprof-fill{height:100%;border-radius:7px;background:var(--brand-grad);width:0;transition:width .6s ease}
.fprof-driver{font-family:var(--mono);font-size:10.5px;color:var(--muted);margin-top:2px}
.fprof-pct{font-family:var(--mono);font-size:12px;color:var(--muted);text-align:right}
.form-warn{font-size:12.5px;color:#E0913C;background:rgba(224,145,60,.09);border:1px solid rgba(224,145,60,.34);border-radius:9px;padding:9px 12px;margin:4px 0 6px;line-height:1.5}
.gap-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin:4px 0 10px}
@media(max-width:640px){.gap-grid{grid-template-columns:1fr}}
.gap-box{border:1px solid var(--line);border-radius:10px;padding:11px 13px;background:var(--surface)}
.gap-box h4{font-family:var(--mono);font-size:11px;text-transform:uppercase;letter-spacing:.05em;margin:0 0 8px;color:var(--brand-2)}
.gap-item{font-size:12.5px;color:var(--ink);margin:0 0 7px;line-height:1.45}
.gap-item .gap-note{font-weight:650;text-transform:capitalize}
.gap-item .gap-fix{color:var(--muted)}
.gap-item.on-target{color:var(--muted)}
.form-gate{font-size:11.5px;color:var(--muted);line-height:1.55;margin:9px 2px 2px;border-top:1px solid var(--line);padding-top:9px}
.form-gate b{color:var(--cream)}
.form-ing-strip{display:flex;flex-wrap:wrap;gap:8px;margin:2px 0 6px}
.form-ing{display:flex;gap:9px;align-items:center;border:1px solid var(--line);border-radius:9px;padding:6px 10px 6px 6px;background:var(--surface);cursor:pointer;max-width:260px}
.form-ing .nb-struct svg{display:block}
.form-ing .fi-name{font-size:12.5px;font-weight:600;color:var(--ink)}
.form-ing .fi-meta{font-family:var(--mono);font-size:10.5px;color:var(--muted)}
/* compare molecules */
.cmp-inputs{display:flex;flex-wrap:wrap;gap:10px;align-items:center;margin:0 0 12px}
.cmp-in{flex:1;min-width:200px;font:inherit;font-size:13.5px;padding:9px 12px;border:1px solid var(--line);border-radius:8px;background:var(--panel);color:var(--ink)}
.cmp-in:focus{outline:none;border-color:var(--brand-2)}
.cmp-vs{font-family:'Cinzel Decorative',Georgia,serif;color:var(--cream);font-size:13px}
.cmp-results{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-top:14px}
@media(max-width:760px){.cmp-results{grid-template-columns:1fr}}
.cmp-card{border:1px solid var(--line);border-radius:12px;overflow:hidden;background:#0F1319;min-height:120px;cursor:pointer;transition:border-color .14s}
.cmp-card:hover{border-color:var(--brand-2)}
.cmp-card img{display:block;width:100%;height:auto}
.cmp-card .empty{padding:20px}
.cmp-card.loading{display:flex;align-items:center;justify-content:center;min-height:300px}
.cmp-card.loading img{display:none}
.cmp-card:not(.loading) .cmp-load{display:none}
.cmp-load{display:flex;flex-direction:column;align-items:center;gap:8px}
.cmp-load .loader-flask{width:96px;height:96px}
.cmp-load-cap{font-family:'Cinzel',Georgia,serif;font-size:12.5px;letter-spacing:.04em;color:var(--cream)}
.cmp-err{padding:26px 20px;color:var(--muted);font-size:13px;text-align:center}
.mix-ings{display:flex;flex-direction:column;gap:6px;margin-bottom:8px}
.mix-ing{display:flex;gap:10px;align-items:center;padding:8px;border:1px solid var(--line);border-radius:8px;background:var(--surface)}
.mix-ing:hover{background:var(--accent-soft)}
.hazard{border:1px solid #E7C4B8;background:#FBEDE9;color:#8A3A22;border-radius:8px;padding:10px 13px;margin-bottom:8px;font-size:13px;line-height:1.5}
.hazard.cond{border-color:#E8D38A;background:#FFF6E6;color:#7A5A12}
.suggest-dd{position:absolute;z-index:60;background:var(--panel);border:1px solid var(--line);border-radius:9px;box-shadow:0 8px 24px #00000022;max-height:340px;overflow:auto}
.sg-row{display:flex;gap:10px;align-items:center;padding:8px 10px;cursor:pointer;border-bottom:1px solid var(--line)}
.sg-row:last-child{border-bottom:none}
.sg-row:hover{background:var(--accent-soft)}
.sg-st{flex:0 0 auto;width:56px;height:42px;background:var(--struct-bg);border:1px solid var(--line);border-radius:5px}
/* --- dark-mode surface fixes (molecule drawings stay on light 'paper' --struct-bg) --- */
.cat,.st-tab,.mini-3d,.map-reset,.suggest-dd{background:var(--panel)}
.structure,.structure3d,.neighbor .nb-struct,.bcell .bstruct{background:var(--struct-bg)}
.bcell{background:var(--panel)}
.track,.atrack,.tag.predicted{background:#242C35}
#flavorMap{background:#0C1016}
.odor-note{background:#1F2831;border-color:#2E3742}
.aroma-card{background:linear-gradient(180deg,#17282A,#181D24)!important;border-color:#2C4A44!important}
.err{background:#3A1E18;border-color:#5A2E22;color:#F0C6BA}
.cat.on{color:#fff}
.sg-st svg{width:56px;height:42px}
/* --- flavor designer --- */
.design-section{margin:22px 0 4px;border:1px solid #2C6E74;border-radius:12px;padding:15px 18px;background:linear-gradient(180deg,#15211F,#181D24)}
.design-head{font-size:12px;font-weight:650;text-transform:uppercase;letter-spacing:.04em;color:var(--brand-2);cursor:pointer}
.design-head .mapsub{font-weight:600;text-transform:none;letter-spacing:0;color:var(--muted);opacity:.85}
/* --- gilded section headers (alchemy theme): Cinzel wordmark in a gold->teal->violet
gradient, with a small alembic glyph before the title. Subtitles keep their muted color. */
.design-head,.map-head,.browse-head,.toggle{font-family:'Cinzel Decorative','Cinzel',Georgia,serif;
font-size:14px;font-weight:700;text-transform:none;letter-spacing:.03em;
background:linear-gradient(95deg,#E8C48A 0%,#57D0CB 52%,#9B82E6 100%);
-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
.design-head .mapsub,.map-head .mapsub,.browse-head .mapsub{font-family:var(--ui);
-webkit-text-fill-color:var(--muted);background:none;font-size:12px}
.design-head>span,.map-head>span,.toggle>span{-webkit-text-fill-color:#57D0CB} /* carets stay teal */
.design-chips{display:flex;flex-wrap:wrap;gap:6px;margin:12px 0 10px}
.dchip{font:inherit;font-size:12px;padding:4px 11px;border-radius:20px;border:1px solid var(--line);background:transparent;color:var(--ink);cursor:pointer;text-transform:capitalize}
.dchip:hover{border-color:var(--brand-2)}
.dchip.on{background:var(--brand-2);color:#08121A;border-color:var(--brand-2);font-weight:650}
.design-controls{display:flex;align-items:center;gap:14px;flex-wrap:wrap;margin-top:4px}
.design-gras{font-size:12px;color:var(--muted);display:flex;align-items:center;gap:6px;cursor:pointer}
.design-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:12px;margin-top:14px}
.dcell{border:1px solid var(--line);border-radius:9px;padding:10px;background:var(--panel)}
.dstruct{height:78px;background:var(--struct-bg);border-radius:6px;text-align:center;position:relative}.dstruct svg{height:78px;max-width:100%}
.dstruct.is3d{height:130px}
.dcell{position:relative}
.dname{font-weight:600;font-size:13px;margin-top:7px;cursor:pointer;line-height:1.25}
.dname:hover{color:var(--brand-2)}
.dbadge{font-size:9px;font-weight:700;padding:1px 6px;border-radius:10px;background:var(--aroma);color:#08121A;margin-left:6px;text-transform:uppercase}
.dmatch{display:flex;flex-wrap:wrap;gap:4px;margin-top:5px}
.dmatch .mt{font-size:10px;padding:1px 7px;border-radius:20px;text-transform:capitalize}
.dsubs{font-size:11px;color:var(--muted);margin-top:7px;border-top:1px solid var(--line);padding-top:6px;line-height:1.5}
.dsubs a{color:var(--brand-2);cursor:pointer}.dsubs a:hover{text-decoration:underline}
.design-pager{display:flex;align-items:center;justify-content:center;gap:14px;margin-top:10px;font-size:12px;color:var(--muted)}
.design-pager .pg{font:inherit;font-size:12px;padding:4px 12px;border:1px solid var(--line);border-radius:6px;background:var(--panel);color:var(--ink);cursor:pointer}
.design-pager .pg:disabled{opacity:.4;cursor:default}.design-pager .pg:not(:disabled):hover{border-color:var(--brand-2)}
/* --- flavor studio (unified flavors + notes picker) --- */
.studio-nl{display:flex;gap:8px;margin:14px 0 4px}
.studio-nl-input{flex:1;font:inherit;font-size:13.5px;padding:10px 14px;border:1px solid var(--line);
border-radius:9px;background:var(--panel);color:var(--ink)}
.studio-nl-input:focus{outline:none;border-color:var(--brand-2)}
.studio-nl-msg{font-size:12px;color:var(--cream);min-height:0;margin:2px 2px 0}
.studio-hint{font-size:12px;color:var(--muted);margin:10px 2px 8px}
.studio-legend{display:flex;flex-direction:column;gap:4px;margin:10px 2px 10px;padding:10px 12px;border:1px solid var(--line);border-radius:12px;background:linear-gradient(180deg,rgba(138,107,224,.06),rgba(43,196,196,.05))}
.studio-legend span{font-size:12px;color:var(--muted);line-height:1.45}
.studio-legend b{color:var(--ink);font-weight:650}
.studio-legend i{color:var(--cream);font-style:normal}
.studio-legend-tip{margin-top:2px;color:var(--accent)!important}
.studio-chips{display:flex;flex-direction:column;gap:12px;max-height:340px;overflow-y:auto;padding:2px 4px 6px}
.studio-section{display:flex;flex-direction:column;gap:8px}
.ss-head{display:flex;align-items:baseline;gap:8px;font-size:13px;font-weight:700;color:var(--ink);padding-bottom:6px;border-bottom:1px solid var(--line)}
.ss-sub{font-size:11px;font-weight:400;color:var(--muted);text-transform:none;letter-spacing:0}
.ss-body{display:flex;flex-direction:column;gap:7px}
.studio-group{display:flex;flex-wrap:wrap;gap:6px;align-items:baseline}
.studio-group>.sg-label{display:inline-flex;align-items:center;justify-content:flex-end;gap:5px;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--muted);width:96px;flex:none;text-align:right;padding-right:4px}
.sg-ico{display:inline-flex;line-height:0}
.studio-group-notes{margin-left:0}
.ic-sprite{position:absolute;width:0;height:0;overflow:hidden}
.ic{width:1.05em;height:1.05em;fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round;vertical-align:-.15em;flex:none}
.ic circle{fill:none}
.ss-head .ic{width:16px;height:16px;color:var(--accent)}
.sg-ico .ic{width:15px;height:15px;color:var(--muted)}
.studio-legend .ic{width:15px;height:15px;color:var(--accent);vertical-align:-.2em}
.studio-count{display:inline-flex;align-items:center;justify-content:center;min-width:0;width:0;height:18px;margin-left:0;padding:0;overflow:hidden;border-radius:9px;background:rgba(8,18,26,.28);color:inherit;font-size:11px;font-weight:700;opacity:0;transition:all .16s ease}
.studio-count.on{min-width:18px;width:auto;padding:0 6px;margin-left:8px;opacity:1}
.schip{font:inherit;font-size:12px;padding:3px 11px;border:1px solid var(--line);border-radius:20px;background:var(--panel);color:var(--ink);cursor:pointer;text-transform:capitalize}
.schip:hover{border-color:var(--brand-2)}
/* one filter across all 300+ chips, plus perceptual family blocks so notes are scannable */
.chip-filter{position:relative;margin:0 0 12px}
.chip-filter input{width:100%;font:inherit;font-size:13px;padding:9px 13px;border-radius:9px;
border:1px solid var(--line);background:var(--panel);color:var(--ink)}
.chip-filter input:focus{outline:none;border-color:var(--brand-2)}
.cf-none{position:absolute;right:12px;top:50%;transform:translateY(-50%);font-family:var(--mono);
font-size:11px;color:var(--muted)}
.fam{margin:0 0 10px}
.fam-label{font-family:var(--mono);font-size:10.5px;letter-spacing:.07em;text-transform:uppercase;
color:var(--muted);margin:0 0 5px;display:flex;align-items:baseline;gap:7px}
.fam-n{font-size:10px;opacity:.6}
.schip-note{border-style:dashed}
/* mouthfeel chips: a distinct dotted edge + amber cast so the sensation modality reads apart
from odour notes at a glance (cooling/pungent legitimately appear in both rows) */
.schip-mouth{border-style:dotted;border-color:var(--accent);color:var(--accent)}
.schip-mouth:hover{border-color:var(--brand-2)}
.schip.on{background:var(--brand-grad);color:#08121A;border-color:transparent;font-weight:650;box-shadow:0 2px 12px rgba(43,196,196,.32);transform:translateY(-1px)}
.schip{transition:all .13s ease}
.design-grid .dcell{animation:fadeUp .3s ease both}
.design-grid .dcell:nth-child(2){animation-delay:.03s}.design-grid .dcell:nth-child(3){animation-delay:.06s}
.design-grid .dcell:nth-child(4){animation-delay:.09s}.design-grid .dcell:nth-child(n+5){animation-delay:.12s}
.studio-clear{font:inherit;font-size:12px;padding:6px 12px;border:1px solid var(--line);border-radius:8px;background:transparent;color:var(--muted);cursor:pointer}
.studio-clear:hover{border-color:var(--brand-2);color:var(--ink)}
/* --- flavor library --- */
.lib-cats{display:flex;flex-direction:column;gap:9px;margin:12px 0 2px}
.lib-cat{display:flex;flex-wrap:wrap;gap:6px;align-items:baseline}
.lib-cat > .lc-label{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--muted);width:74px;flex:none;text-align:right;padding-right:4px}
.fchip{font:inherit;font-size:12px;padding:3px 11px;border:1px solid var(--line);border-radius:20px;background:var(--panel);color:var(--ink);cursor:pointer;text-transform:capitalize}
.fchip:hover{border-color:var(--brand-2);color:var(--brand-2)}
.fchip.on{background:var(--brand-grad);color:#08121A;border-color:transparent;font-weight:650}
.sg-info{min-width:0}
.sg-name{font-weight:600;font-size:13px}
.sg-iupac{font-family:var(--mono);font-size:10px;color:var(--muted);word-break:break-all}
.sg-smi{font-family:var(--mono);font-size:11px;color:var(--accent);word-break:break-all}
/* --- enrichment table --- */
.enrich-controls{display:flex;align-items:center;gap:12px;margin:12px 0 8px}
.enrich-search{flex:1;max-width:420px;font:inherit;font-size:13px;padding:8px 12px;border:1px solid var(--line);border-radius:8px;background:var(--panel);color:var(--ink)}
.enrich-search:focus{outline:none;border-color:var(--brand-2)}
.enrich-count{font-size:12px;color:var(--muted)}
/* parchment accent: aged-paper header + bronze frame; data rows stay dark & crisp */
.enrich-scroll{overflow:auto;max-height:74vh;border:1px solid #6E5334;border-radius:10px;
box-shadow:inset 0 0 0 1px rgba(235,203,146,.10)} /* scrolls both ways */
.enrich-table{width:100%;border-collapse:collapse;font-size:12.5px;min-width:760px}
.enrich-table th{position:sticky;top:0;z-index:6;text-align:left;padding:10px 11px;font-weight:700;
background:linear-gradient(180deg,#EEE1C6 0%,#E0D0AA 100%);color:#5A4326;
border-bottom:2px solid #B79A63;cursor:pointer;white-space:nowrap;user-select:none;letter-spacing:.02em;
text-shadow:0 1px 0 rgba(255,255,255,.35)}
.enrich-table td.et-struct,.et-structwrap,.mini-3d.et-3d{z-index:1} /* keep structure cells under the sticky header */
.enrich-table th:hover{color:#2E2010}
.enrich-table th .th-ar{opacity:.75;font-size:10px;color:#7A5A2E}
.enrich-table td{padding:8px 11px;border-bottom:1px solid #20272F;color:var(--ink);white-space:nowrap}
.enrich-table tbody tr{cursor:pointer}
.enrich-table tbody tr:hover{background:rgba(43,196,196,.06)}
.enrich-table .et-name{font-weight:600}
.enrich-table .et-mut{color:var(--muted)}
.et-gras{font-size:9px;font-weight:700;padding:1px 6px;border-radius:9px;background:var(--aroma);color:#08121A}
.nb-gras{font-size:8.5px;font-weight:700;padding:1px 6px;border-radius:9px;background:var(--aroma);color:#08121A;margin-left:6px;vertical-align:middle;letter-spacing:.03em}
.et-iso{font-size:9px;font-weight:700;padding:1px 6px;border-radius:9px;background:rgba(217,171,116,.22);color:var(--cream);margin-left:5px}
.et-tag{font-size:10px;padding:1px 7px;border-radius:20px;text-transform:capitalize}
.enrich-table th.nosort{cursor:default}
.enrich-table th.nosort:hover{color:var(--brand-2)}
.enrich-table td.et-struct{padding:3px 6px;background:var(--struct-bg);width:116px}
.enrich-table td.et-struct svg{display:block;width:104px;height:62px;border-radius:4px}
.et-structwrap{position:relative;width:104px;height:62px}
.et-3dbox{width:104px;height:62px}
.et-3dbox.is3d{border-radius:4px;overflow:hidden}
.et-3dbox.is3d canvas{border-radius:4px}
.mini-3d.et-3d{top:2px;right:2px;padding:0 4px;font-size:9px;line-height:15px}
.et-clip{display:inline-block;max-width:240px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:middle}
.et-smiles{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;color:var(--muted);max-width:200px}
/* --- polish: motion, hover, responsive --- */
.card,.design-section,.browse,#mixPanel{transition:border-color .18s ease,box-shadow .18s ease}
.design-section:hover,.browse:hover{box-shadow:0 6px 26px rgba(0,0,0,.22)}
.bcell,.dcell,.stereo-cell{transition:transform .14s ease,border-color .14s ease,box-shadow .14s ease}
.bcell:hover,.dcell:hover,.stereo-cell:hover{transform:translateY(-2px);box-shadow:0 8px 22px rgba(0,0,0,.28)}
.bar button,.mix-btn,.st-tab,.cat,.schip,.fchip,.map-reset,.pg,.studio-mode{transition:all .14s ease}
.bar button:hover:not(:disabled){filter:brightness(1.08)}
.design-head,.browse-head,.toggle,.map-head,.mapToggle{transition:color .15s ease}
#flavorMap{max-width:100%;height:auto}
.map-canvas-wrap{max-width:100%}
#flavorMap{width:100%;height:auto;display:block} /* fill the content width like the rest of the page */
/* smooth reveal for result cards */
@keyframes fadeUp{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
#results .card{animation:fadeUp .28s ease both}
/* dashboard modal: read+heads on top (2-col wide), then behavior, then the two swap sections */
#results{display:flex;flex-direction:column;gap:14px} /* tight, consistent gap between all cards (not 0) */
/* left column (read + behavior stacked) and the tall Heads card share equal height; the Heads card
scrolls its own overflow so its bottom meets the behavior card's bottom, never overshooting */
.modal-top{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:14px;align-items:stretch}
.modal-left{display:flex;flex-direction:column;gap:14px;min-width:0}
.heads-card{display:flex;flex-direction:column;gap:13px;min-height:0;overflow-y:auto}
/* aroma group grows to fill the Heads card down to the height of the left column, scrolling inside */
#aromaGroup{display:flex;flex-direction:column;min-height:0}
.head-group{border-top:1px solid var(--line);padding-top:11px}
.head-group:first-of-type{border-top:none;padding-top:0}
.hg-label{font-family:var(--mono);font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);margin-bottom:8px;display:flex;align-items:baseline;gap:8px}
.hg-hint{font-size:10px;letter-spacing:.02em;text-transform:none;color:var(--muted);opacity:.7}
#aromaPreview{max-height:300px;overflow-y:auto;overflow-x:hidden;margin:0 -4px;padding:0 4px}
/* compact head bar for the mouthfeel + safety groups (reuses the aroma meter look) */
.hbar{display:flex;align-items:center;gap:9px;padding:3px 0;font-size:12.5px}
.hbar .hb-name{flex:0 0 118px;color:var(--ink)}
.hbar.dim .hb-name{color:var(--muted)}
.hbar .hb-track{flex:1;height:7px;border-radius:4px;background:var(--line);overflow:hidden}
.hbar .hb-fill{height:100%;border-radius:4px}
.hbar .hb-val{flex:0 0 auto;font-family:var(--mono);font-size:11px;color:var(--muted);white-space:nowrap}
.hbar .hb-au{color:var(--muted);opacity:.75}
.hg-note{font-size:11px;color:var(--muted);margin:6px 0 0;line-height:1.5}
#aromaPreview::-webkit-scrollbar{width:8px}#aromaPreview::-webkit-scrollbar-thumb{background:var(--line);border-radius:4px}
/* the two swap lists fill their box (bounded + internal scroll) so no wasted whitespace */
.swap-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px;align-items:stretch}
.swap-grid .card{display:flex;flex-direction:column;min-height:360px;max-height:70vh}
.swap-grid .card .nb-list{flex:1 1 auto;min-height:0;max-height:none}
@media(max-width:820px){ .modal-top{grid-template-columns:1fr} .swap-grid{grid-template-columns:1fr} .swap-grid .card{max-height:none} }
/* mobile */
@media(max-width:640px){
header{padding:22px 14px 18px}
header h1{font-size:34px}
header .sub{font-size:11.5px;letter-spacing:.09em}
.bar{flex-direction:column}
.bar input{font-size:13.5px;padding:12px 13px}
.bar button{padding:12px}
.hint{font-size:11px}
.tour{gap:6px}.tour-chip{font-size:12px;padding:3px 10px}
.mapsub{font-size:11px;display:block;margin-top:3px}
.map-axis-key{font-size:10.5px;gap:8px}
.grid{gap:14px}
.card{padding:16px 15px}
.design-section,.browse{padding:13px 13px}
.map-controls{gap:6px 8px}
.studio-chips{max-height:220px}
h1,h2,.design-head,.map-head{overflow-wrap:anywhere}
.mol-modal{padding:16px 8px}
.mol-modal-inner{padding:54px 13px 20px}
}
/* --- molecule modal + logo-matched loader --- */
.mol-modal{position:fixed;inset:0;z-index:200;background:rgba(6,10,14,.72);backdrop-filter:blur(4px);
display:flex;align-items:center;justify-content:center;overflow:hidden auto;padding:34px 16px} /* clip x so the scroll rods never overflow the viewport (was zooming mobile out) */
.mol-modal-inner{position:relative;width:100%;max-width:1000px;margin:auto;background:var(--surface);
border:1px solid var(--line);border-radius:16px;box-shadow:0 24px 80px rgba(0,0,0,.55);padding:60px 22px 26px;
animation:modalIn .22s cubic-bezier(.2,.9,.3,1)} /* generous top pad clears the centered action row + buffer */
.mol-modal-inner::before{content:'';position:absolute;inset:0;border-radius:16px;padding:1px;pointer-events:none;
background:linear-gradient(135deg,rgba(124,92,191,.5),rgba(43,196,196,.5));
-webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;mask-composite:exclude;opacity:.6}
@keyframes modalIn{from{opacity:0;transform:translateY(14px) scale(.985)}to{opacity:1;transform:none}}
/* --- scroll frame: bronze/gilded rolled rods at the top & bottom of the modal (an unrolled
scroll), with knobbed end-caps β a parchment/scroll flourish kept dark-theme-friendly --- */
.scroll-rod{position:absolute;left:-13px;right:-13px;height:19px;pointer-events:none;z-index:2;border-radius:10px;
background:linear-gradient(180deg,#5A3E24 0%,#B98A54 20%,#EBCB92 44%,#B98A54 68%,#4E351F 100%);
box-shadow:0 3px 11px rgba(0,0,0,.5)}
.scroll-rod::before,.scroll-rod::after{content:'';position:absolute;top:50%;transform:translateY(-50%);
width:15px;height:25px;border-radius:50%;
background:radial-gradient(circle at 40% 38%,#EBCB92,#6B4A2A 72%,#4E351F);box-shadow:0 2px 6px rgba(0,0,0,.5)}
.scroll-rod::before{left:-7px}.scroll-rod::after{right:-7px}
.scroll-rod-top{top:-11px}.scroll-rod-bottom{bottom:-11px}
@media(max-width:640px){.scroll-rod{left:3px;right:3px}.scroll-rod::before{left:-4px}.scroll-rod::after{right:-4px}}
/* wax-seal close */
.mol-close{position:absolute;top:14px;right:16px;z-index:4;width:38px;height:38px;border-radius:50%;
border:1px solid #8A2A38;background:radial-gradient(circle at 38% 34%,#C0455A,#7A1F2E 68%,#571623);
color:#F0D9A8;font-size:15px;cursor:pointer;line-height:1;
box-shadow:0 3px 9px rgba(0,0,0,.5),inset 0 1px 2px rgba(255,255,255,.18),inset 0 -2px 5px rgba(0,0,0,.45)}
.mol-close:hover{filter:brightness(1.12);color:#FBEFCF}
/* function buttons: centered as a group at the top; the close X stays top-right */
.mol-actions{position:absolute;top:14px;left:50%;transform:translateX(-50%);z-index:4;display:flex;gap:8px}
.mol-share{font:inherit;font-size:11px;font-weight:600;
padding:5px 11px;border-radius:16px;border:1px solid var(--line);background:var(--panel);color:var(--muted);cursor:pointer}
.mol-share:hover{border-color:var(--brand-2);color:var(--ink)}
.mol-modal.loading .mol-actions{display:none} /* reveal only once the read has finished rendering */
@media(max-width:640px){.mol-share{padding:4px 9px;font-size:10px}}
.mol-loader{display:flex;flex-direction:column;align-items:center;gap:6px;padding:34px 16px 40px}
.loader-flask{width:150px;height:150px}
.loader-ring{transform-origin:70px 70px;animation:ringspin 1.15s linear infinite}
@keyframes ringspin{to{transform:rotate(360deg)}}
.loader-flask .bub{opacity:0;transform-box:fill-box;transform-origin:center;animation:bub 1.7s ease-in infinite}
.loader-flask .b1{animation-delay:0s}.loader-flask .b2{animation-delay:.5s}
.loader-flask .b3{animation-delay:.9s}.loader-flask .b4{animation-delay:1.3s}
@keyframes bub{0%{opacity:0;transform:translateY(6px) scale(.4)}20%{opacity:.9}100%{opacity:0;transform:translateY(-18px) scale(.95)}}
.loader-flask .wisp{stroke-dasharray:5 9;transform-box:fill-box;transform-origin:bottom;animation:wisp 2.2s linear infinite, wispRise 3.4s ease-in-out infinite}
.loader-flask .w1{opacity:.85;animation-delay:0s,0s}
.loader-flask .w2{opacity:.6;animation-delay:.5s,.4s}
.loader-flask .w3{opacity:.55;animation-delay:1s,.9s}
.loader-flask .w4{opacity:.5;animation-delay:1.5s,1.3s}
@keyframes wisp{to{stroke-dashoffset:-28}}
@keyframes wispRise{0%,100%{transform:translateY(2px) scaleY(.96)}50%{transform:translateY(-2px) scaleY(1.02)}}
.loader-title{font-family:'Cinzel Decorative',Georgia,serif;font-size:15px;letter-spacing:.06em;color:var(--cream);margin-top:6px}
/* rotating flavor-wizardry cantrip between the title and the step list */
.loader-cantrip{font-family:'Cinzel Decorative',Georgia,serif;font-size:12.5px;letter-spacing:.04em;
color:var(--brand-2);min-height:1.2em;margin-top:2px;opacity:0;transition:opacity .32s ease}
.loader-cantrip.show{opacity:.92}
/* out-of-corpus banner: fades in above the flask when the molecule isn't precomputed */
.loader-corpus-note{max-width:420px;margin:0 auto 4px;padding:8px 13px;border-radius:11px;font-size:12px;line-height:1.5;
color:var(--cream);background:rgba(217,171,116,.10);border:1px solid rgba(217,171,116,.35);
opacity:0;transform:translateY(-4px);transition:opacity .4s ease,transform .4s ease;display:none}
.loader-corpus-note.show{display:block;opacity:1;transform:none}
.loader-steps{list-style:none;margin:14px auto 0;padding:0;display:flex;flex-direction:column;gap:7px;min-width:250px}
.loader-steps li{display:flex;align-items:center;justify-content:center;gap:9px;font-size:12.5px;color:var(--muted);transition:color .2s}
.loader-steps li.done{color:var(--ink)}
.ls-dot{width:13px;height:13px;border-radius:50%;border:2px solid var(--line);flex:none;position:relative;transition:all .2s}
li.active .ls-dot{border-color:var(--brand-2);border-top-color:transparent;animation:ringspin .7s linear infinite}
li.done .ls-dot{border-color:var(--aroma);background:var(--aroma)}
li.done .ls-dot::after{content:'β';position:absolute;inset:-3px 0 0 1px;font-size:10px;color:#08121A;font-weight:800}
/* --- sources footer --- */
footer.sources{border-top:1px solid var(--line);margin-top:40px;padding:30px 28px 46px;
background:radial-gradient(120% 140% at 50% 130%, #1C2836 0%, var(--surface) 60%)}
.src-inner{max-width:980px;margin:0 auto;display:grid;grid-template-columns:1.15fr 1fr;gap:34px}
@media(max-width:720px){.src-inner{grid-template-columns:1fr;gap:20px}}
footer.sources h4{margin:0 0 10px;font-family:'Cinzel Decorative',Georgia,serif;font-size:12px;
text-transform:uppercase;letter-spacing:.12em;color:var(--cream)}
footer.sources ul{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:7px}
footer.sources li{font-size:11.5px;line-height:1.5;color:var(--muted)}
footer.sources li b{color:var(--ink);font-weight:600}
footer.sources .pd,.src-col .ac,.src-col .lic{font-size:9px;font-weight:700;text-transform:uppercase;
letter-spacing:.04em;padding:1px 6px;border-radius:9px;margin-left:3px;white-space:nowrap}
footer.sources .pd{background:rgba(43,196,196,.14);color:var(--brand-2)}
footer.sources .ac{background:rgba(217,171,116,.16);color:var(--cream)}
footer.sources .lic{background:rgba(255,255,255,.06);color:var(--muted)}
.src-note{font-size:11px;color:var(--muted);line-height:1.55;margin:14px 0 0;
border-top:1px solid var(--line);padding-top:12px}
.src-foot{max-width:980px;margin:26px auto 0;padding-top:22px;text-align:center;
border-top:1px solid var(--line);display:flex;flex-direction:column;gap:9px}
.foot-tag{font-size:10.5px;letter-spacing:.06em;color:var(--muted);opacity:.85}
.foot-brand{font-family:'Cinzel Decorative',Georgia,serif;letter-spacing:.04em;
background:var(--brand-grad);-webkit-background-clip:text;background-clip:text;color:transparent}
.foot-credit{display:flex;flex-wrap:wrap;justify-content:center;align-items:center;gap:9px;
font-size:12px;color:var(--muted)}
.foot-credit a{color:var(--brand-2);text-decoration:none;border-bottom:1px solid transparent;transition:border-color .15s}
.foot-credit a:hover{border-bottom-color:var(--brand-2)}
.foot-credit a b{color:var(--ink);font-weight:650}
.foot-dot{opacity:.4}
.foot-copy{font-size:10px;letter-spacing:.08em;color:var(--muted);opacity:.6}
.foot-copy i{color:var(--cream);font-style:normal}
</style>
</head>
<body>
<!-- Icon sprite: single-weight line glyphs, stroke=currentColor so they inherit the theme.
Referenced as <svg class="ic"><use href="#ic-NAME"/></svg>. No emoji anywhere. -->
<svg class="ic-sprite" width="0" height="0" aria-hidden="true" focusable="false"><defs>
<symbol id="ic-fruit" viewBox="0 0 24 24"><path d="M12 8.2C11 6.4 8.8 5.6 7 6.6 5.2 7.6 4.8 10.4 5.8 13.2 6.7 15.8 8.8 19 12 19s5.3-3.2 6.2-5.8c1-2.8.6-5.6-1.2-6.6-1.8-1-4-.2-5 1.6Z"/><path d="M12 8.2V5.2"/><path d="M12.4 6.4C13.2 4.6 15.2 4.2 16.8 4.6 16.9 6.2 15.9 7.6 14 7.6"/></symbol>
<symbol id="ic-citrus" viewBox="0 0 24 24"><circle cx="12" cy="12" r="7.4"/><circle cx="12" cy="12" r="1.2"/><path d="M14.2 12H18.4M13.1 13.9 15.2 17.5M10.9 13.9 8.8 17.5M9.8 12H5.6M10.9 10.1 8.8 6.5M13.1 10.1 15.2 6.5"/></symbol>
<symbol id="ic-sweet" viewBox="0 0 24 24"><circle cx="12" cy="12" r="4"/><path d="M8 12 4 9.3V14.7ZM16 12 20 9.3V14.7Z"/></symbol>
<symbol id="ic-vegetal" viewBox="0 0 24 24"><path d="M6 18C6 11 11 6 18 6 18 13 13 18 6 18Z"/><path d="M8 16 16 8"/></symbol>
<symbol id="ic-herb" viewBox="0 0 24 24"><path d="M12 20V5"/><path d="M12 8C10.5 8 9.4 6.9 9.4 5.4 10.9 5.4 12 6.5 12 8Z"/><path d="M12 11.5C13.7 11.5 15 10.2 15 8.5 13.3 8.5 12 9.8 12 11.5Z"/><path d="M12 14.5C10.3 14.5 9 13.2 9 11.5 10.7 11.5 12 12.8 12 14.5Z"/></symbol>
<symbol id="ic-floral" viewBox="0 0 24 24"><circle cx="12" cy="6.6" r="2.4"/><circle cx="16.3" cy="9.7" r="2.4"/><circle cx="14.6" cy="14.7" r="2.4"/><circle cx="9.4" cy="14.7" r="2.4"/><circle cx="7.7" cy="9.7" r="2.4"/><circle cx="12" cy="11.3" r="1.7"/></symbol>
<symbol id="ic-savory" viewBox="0 0 24 24"><path d="M4 12H20C20 16 16.5 19 12 19S4 16 4 12Z"/><path d="M3 12H21"/><path d="M9 8.5C9 7 10.5 7 10.5 5.5M14 8.5C14 7 15.5 7 15.5 5.5"/></symbol>
<symbol id="ic-spice" viewBox="0 0 24 24"><path d="M16 9C13 9 9 11 9 15c0 3 2 5 5 4 4-1.4 5-8 2-10Z"/><path d="M16 9c-1-1-1.5-3-1-5"/></symbol>
<symbol id="ic-dairy" viewBox="0 0 24 24"><path d="M4 16 18 9c1.4-.7 2.6.5 2.2 1.9L19 16c-.2.7-.8 1-1.5 1H5.5C4 17 3.3 16.7 4 16Z"/><path d="M4 16 18 9"/><circle cx="9" cy="14" r="1"/><circle cx="13.5" cy="13" r=".8"/></symbol>
<symbol id="ic-nut" viewBox="0 0 24 24"><path d="M6.5 9C6.5 6 17.5 6 17.5 9Z"/><path d="M6.5 9H17.5"/><path d="M7.5 9C7.5 13.5 9.5 18 12 18S16.5 13.5 16.5 9"/><path d="M12 6V4"/></symbol>
<symbol id="ic-roast" viewBox="0 0 24 24"><path d="M12 5C15.5 5 18 8 18 12S15.5 19 12 19 6 16 6 12 8.5 5 12 5Z"/><path d="M12 5.5C10.4 8 10.4 16 12 18.5"/></symbol>
<symbol id="ic-allium" viewBox="0 0 24 24"><path d="M12 3.5C9.8 8 6.8 10.2 6.8 14.2 6.8 17.7 9.1 20.2 12 20.2S17.2 17.7 17.2 14.2C17.2 10.2 14.2 8 12 3.5Z"/><path d="M12 5.5C10.7 9.5 9.7 13 10.2 20M12 5.5C13.3 9.5 14.3 13 13.8 20"/></symbol>
<symbol id="ic-other" viewBox="0 0 24 24"><path d="M9 3H15M10 3V9L5.5 17.5C5 18.5 5.7 20 7 20H17C18.3 20 19 18.5 18.5 17.5L14 9V3"/><path d="M7.4 15H16.6"/></symbol>
<symbol id="ic-flavors" viewBox="0 0 24 24"><path d="M12 3C9.5 3 7.8 5.3 7.8 8.1 7.8 10.9 9.5 13.2 12 13.2S16.2 10.9 16.2 8.1 14.5 3 12 3Z"/><path d="M12 13.2V21"/></symbol>
<symbol id="ic-notes" viewBox="0 0 24 24"><path d="M6 18.5C8 15.5 8 13 6 10M11 19.5C13 16.5 13 13.5 11 10.5M16 18.5C18 15.5 18 13 16 10"/></symbol>
</defs></svg>
<header>
<img class="logo" src="/static/logo.png" alt="Flavormancer logo">
<div class="brand">
<h1>Flavormancer</h1>
<span class="sub">taste & aroma prediction from chemical structure β before you pour</span>
</div>
</header>
<main>
<div class="bar">
<input id="q" placeholder="Compound name or SMILES β e.g. vanillin, or O=Cc1ccc(O)c(OC)c1"
autocomplete="off" spellcheck="false">
<button id="go">Read flavor</button>
</div>
<div class="hint">Type a <b>common or IUPAC name</b> (e.g. <code>vanillin</code>) or a
<b>SMILES</b> string (e.g. <code>O=Cc1ccc(O)c(OC)c1</code>). SMILES omit implicit
hydrogens β water is just <code>O</code>. These models are for <b>organic</b> molecules.</div>
<div class="tour">
<span class="tour-label">New here? Try one:</span>
<button type="button" class="tour-chip" data-q="vanillin">vanillin</button>
<button type="button" class="tour-chip" data-q="CC(=C)[C@H]1CC=C(C)C(=O)C1" title="spearmint β flip it to S for caraway in the chirality explorer">(R)-carvone</button>
<button type="button" class="tour-chip" data-q="menthol">menthol</button>
<button type="button" class="tour-chip" data-q="isoamyl acetate">isoamyl acetate</button>
<button type="button" class="tour-chip" data-q="limonene">limonene</button>
<button type="button" class="tour-chip" data-q="geosmin">geosmin</button>
<button type="button" class="tour-chip" data-q="caffeine">caffeine</button>
<button type="button" class="tour-chip" data-q="furaneol">furaneol</button>
</div>
<div class="design-section" id="howStudio">
<div class="design-head" id="howToggle"><span id="howCaret">βΎ</span> How it works
<span class="mapsub">β what's real, what's predicted, and where it gets sharper. Honest by design; a read runs 100% on-prem.</span></div>
<div id="howWrap">
<div class="how-grid">
<div class="how-card"><h4>1 Β· Structure β numbers</h4><p>Every molecule becomes a <b>2,048-bit Morgan fingerprint</b> (which substructures it contains) plus a block of <b>physicochemical descriptors</b> (logP, MW, TPSA, H-bondingβ¦). That one shared vector feeds every model β the same structure-to-property representation QSAR has leaned on for decades.</p></div>
<div class="how-card"><h4>2 Β· Taste β 6 trained heads + 2 rules</h4><p>Six <b>random-forest</b> classifiers (sweet, bitter, umamiβ¦) each return a probability, scored by an honest held-out <b>CV-AUROC</b>. Sour and salty are solution / ionic effects, not molecule-shape ones, so they're transparent <b>rules</b> β not faked models. A sweetness-intensity regressor estimates relative-to-sucrose potency.</p></div>
<div class="how-card"><h4>3 Β· Aroma β 164 odor heads</h4><p>One random forest per descriptor (citrus, floral, woodyβ¦), trained on <b>public-domain odor text</b>. A head ships only if it clears <b>CV-AUROC β₯ 0.70</b> β 164 survive, each shown with its own score. It reads <b>presence, not intensity</b> (free text carries none) β an honest ceiling, stated in the UI.</p></div>
<div class="how-card"><h4>4 Β· Honest by design</h4><p>Every value is tagged <b>measured / predicted / estimate</b>, so nothing reads as more precise than it is. Where a quantitative feature needs data we can't ship free-commercially (odor thresholds, panel intensities), the UI <b>says so</b> β and it lights up with <b>your</b> data.</p></div>
<div class="how-card"><h4>5 Β· Formulation, not just molecules</h4><p>The <b>Formulation Studio</b> reads a whole recipe before you pour β weighting each ingredient by odor impact, aggregating the blend's note-profile, flagging the overpowering component, and closing the gap to your target. That's single-molecule ML turned into a bench tool.</p></div>
<div class="how-card"><h4>6 Β· On-prem & commercial-clean</h4><p>Nothing leaves the box β a read makes <b>no cloud calls</b>. The shipped models train only on <b>public-domain or permissively-licensed</b> data, so the commercial edition stays clean (provenance tracked in the repo).</p></div>
</div>
<p class="how-foot">Deeper dives in the repo: <a href="https://github.com/echelonts/flavormancer/blob/main/docs/HOW-IT-WORKS.md" target="_blank" rel="noopener">docs/HOW-IT-WORKS.md</a> (the full method) Β· <a href="https://github.com/echelonts/flavormancer/blob/main/docs/DATA-SOURCES.md" target="_blank" rel="noopener">docs/DATA-SOURCES.md</a> (every source + license) Β· <a href="https://github.com/echelonts/flavormancer/blob/main/docs/AROMA.md" target="_blank" rel="noopener">docs/AROMA.md</a> (the odor heads).</p>
</div>
</div>
<div class="design-section" id="studio">
<div class="design-head" id="studioToggle"><span id="studioCaret">βΎ</span> Flavor Studio
<span class="mapsub">β pick any mix of <b>flavors</b> and <b>notes</b> (explained below); Studio ranks the food-safe molecules that carry them, with 2D/3D structures + drop-in swaps.</span></div>
<div id="studioWrap">
<div class="studio-nl">
<input id="studioNl" class="studio-nl-input" autocomplete="off"
placeholder="Describe it β e.g. βa food-safe cherry flavoring with fruity, sweet notesβ β then Interpret">
<button type="button" id="studioNlGo" class="mix-btn">Interpret</button>
</div>
<div id="studioNlMsg" class="studio-nl-msg"></div>
<div class="studio-legend">
<span><b><svg class="ic"><use href="#ic-flavors"/></svg> Flavors</b> β a named flavor you know (banana, saffron) β the molecule that <i>makes</i> it</span>
<span><b><svg class="ic"><use href="#ic-notes"/></svg> Notes</b> β a single aroma/taste <i>quality</i> (citrus, floral, sweet) β molecules that <i>carry</i> it</span>
<span class="studio-legend-tip">Pick any mix β a molecule ranks by how many of your picks it hits.</span>
</div>
<div id="studioChips" class="studio-chips"></div>
<div class="design-controls">
<label class="design-gras"><input type="checkbox" id="designGras" checked> food-listed only</label>
<button class="mix-btn" id="studioGo">Find molecules<span id="studioGoCount" class="studio-count"></span></button>
<button type="button" id="studioClear" class="studio-clear">clear</button>
</div>
<div id="studioResults" class="design-grid"></div>
</div>
</div>
<div class="design-section" id="formStudio">
<div class="design-head" id="formToggle"><span id="formCaret">βΎ</span> Formulation Studio
<span class="mapsub">β the <b>βbefore you pourβ</b> bench read: plug in your whole recipe (ingredients + ppm) and Studio predicts the <b>profile the blend reads as</b>, flags the <b>overpowering component</b>, screens hazards, and β against a <b>target</b> β tells you exactly what's <b>short</b> or <b>over</b> and what to <b>add or cut</b>. Spend lab runs where they count.</span></div>
<div id="formWrap">
<p class="mix-explain">Two ways to work. <b>Analyze</b> β enter a formulation the way it sits on your bench (each ingredient + <b>ppm</b>); every ingredient's predicted aroma is weighted by its <b>odor impact</b> β OAV (ppm Γ· odor threshold) where thresholds are loaded, else <b>mass Γ volatility</b> β then aggregated into the blend's note-profile, with the driving ingredient named under each note, plus an overpowering flag and hazard screen. <b>Design</b> β instead of starting from a blank bench, pick a target (flavors + notes) and Studio proposes a starting recipe: each flavor maps to its character-impact molecule and each note to a food-safe carrier, dosed by <b>inverse volatility</b> (volatile top-notes low, heavy base-notes higher) so nothing dominates, then run through Analyze so you see the predicted profile immediately. <b>Both are directional reads today; they sharpen to calibrated intensity when your odor-threshold / panel data loads</b> (see the notes on each result).</p>
<div class="mix-examples"><span>Try a starter formula:</span>
<button type="button" class="form-ex" data-ings="vanillin:80|ethyl vanillin:20|ethyl maltol:15|limonene:6" data-tgt="sweet,caramel,balsamic">vanilla cream base</button>
<button type="button" class="form-ex" data-ings="limonene:250|citral:40|linalool:25|ethyl butyrate:15" data-tgt="citrus,fresh,fruity">citrus soda top-note</button>
<button type="button" class="form-ex" data-ings="menthol:120|eucalyptol:40|methyl salicylate:20" data-tgt="minty,fresh,camphor">cooling mint</button>
</div>
<div class="form-cols"><span class="form-col-h">ingredient</span><span class="form-col-h form-col-ppm">ppm</span></div>
<div id="formRows"></div>
<button class="mix-add" id="formAdd">+ add ingredient</button>
<div class="mix-proc">Process applied:
<label><input type="checkbox" class="fproc" value="high_heat"> high heat</label>
<label><input type="checkbox" class="fproc" value="refining"> refining</label>
<label><input type="checkbox" class="fproc" value="fermentation"> fermentation</label>
</div>
<button class="mix-btn" id="formGo">Analyze formulation</button>
<div class="form-target">
<div class="form-target-head">Target profile <span class="mapsub" style="text-transform:none;font-weight:600">β optional; pick the <b>flavors</b> and <b>notes</b> you're aiming for. <b>Analyze</b> (above) scores your rows against it; <b>Design</b> (below) proposes a recipe for it.</span></div>
<div class="form-tgt-group"><span class="form-tgt-label"><svg class="ic"><use href="#ic-flavors"/></svg> Flavors</span><div id="formTargetFlavors" class="form-chips"></div></div>
<div class="form-tgt-group"><span class="form-tgt-label"><svg class="ic"><use href="#ic-notes"/></svg> Notes</span><div id="formTargetChips" class="form-chips"></div></div>
<div class="form-tgt-group"><span class="form-tgt-label" title="Chemesthesis β how it feels in the mouth, not how it smells"><svg class="ic"><use href="#ic-notes"/></svg> Mouthfeel</span><div id="formTargetMouth" class="form-chips"></div></div>
<button class="mix-btn" id="formDesign" title="Propose a starting recipe for your target flavors + notes" style="margin-top:4px">β¨ Design a recipe for this target</button>
</div>
<div id="formResults"></div>
</div>
</div>
<label class="toggle"><input type="checkbox" id="mixToggle" checked> <span id="mixCaret">βΎ</span> Mixture screen β flag documented hazards when ingredients combine</label>
<div id="mixPanel">
<p class="mix-explain">Enter two or more ingredients and Flavormancer flags <b>documented
precursorβproduct hazards</b> β combinations known to form a harmful compound (e.g. a
preservative + an acid β a carcinogen). It's a <b>curated safety screen, not a general
reaction predictor</b>: a hazard fires only when the required roles co-occur, and
process-gated ones (heat / refining / fermentation) only when you mark that process.
It also gives a per-ingredient taste read and a single-molecule palette match.</p>
<div class="mix-examples"><span>Try a known bad combo:</span>
<button type="button" class="mix-ex" data-ings="sodium benzoate|ascorbic acid">benzoate + vitamin C β benzene</button>
<button type="button" class="mix-ex" data-ings="sodium nitrite|dimethylamine">nitrite + amine β nitrosamine</button>
<button type="button" class="mix-ex" data-ings="glucose|asparagine" data-proc="high_heat">sugar + asparagine + heat β acrylamide</button>
<button type="button" class="mix-ex" data-ings="ethanol|urea">ethanol + urea β ethyl carbamate</button>
</div>
<div class="mix-examples"><span>Or a flavor-forming combo:</span>
<button type="button" class="mix-ex" data-ings="acetic acid|ethanol">acetic acid + ethanol β ethyl acetate</button>
<button type="button" class="mix-ex" data-ings="butyric acid|ethanol">butyric acid + ethanol β ethyl butyrate (pineapple)</button>
<button type="button" class="mix-ex" data-ings="acetic acid|isoamyl alcohol">acetic acid + isoamyl alcohol β isoamyl acetate (banana)</button>
<button type="button" class="mix-ex" data-ings="benzaldehyde|methylamine">benzaldehyde + methylamine β N-methylbenzaldimine (a Schiff base)</button>
</div>
<div id="mixRows"></div>
<button class="mix-add" id="mixAdd">+ add ingredient</button>
<div class="mix-proc">Process applied:
<label><input type="checkbox" class="proc" value="high_heat"> high heat</label>
<label><input type="checkbox" class="proc" value="refining"> refining</label>
<label><input type="checkbox" class="proc" value="fermentation"> fermentation</label>
</div>
<button class="mix-btn" id="mixCheck">Check mixture</button>
<div id="mixResults"></div>
</div>
<label class="toggle"><input type="checkbox" id="cmpToggle" checked> <span id="cmpCaret">βΎ</span> Compare molecules β two reads side by side</label>
<div id="cmpPanel">
<p class="mix-explain">Enter two molecules to see their <b>flavor cards side by side</b> β compare
taste, aroma, and structure at a glance (great for a reformulation or a chirality pair).</p>
<div class="cmp-inputs">
<input id="cmpA" class="cmp-in" autocomplete="off" placeholder="name or SMILES β e.g. vanillin">
<span class="cmp-vs">vs</span>
<input id="cmpB" class="cmp-in" autocomplete="off" placeholder="name or SMILES β e.g. ethylvanillin">
<button class="mix-btn" id="cmpGo">Compare</button>
</div>
<div class="mix-examples"><span>Try:</span>
<button type="button" class="mix-ex cmp-ex" data-a="vanillin" data-b="ethyl vanillin">vanillin vs ethyl vanillin</button>
<button type="button" class="mix-ex cmp-ex" data-a="(R)-carvone" data-b="(S)-carvone">R- vs S-carvone</button>
<button type="button" class="mix-ex cmp-ex" data-a="limonene" data-b="menthol">limonene vs menthol</button>
</div>
<div id="cmpResults" class="cmp-results"></div>
</div>
<div id="browse" class="browse">
<div class="browse-head">Or browse the library β pick a category, then a molecule (each card has a 2D/3D toggle)</div>
<div id="browseCats" class="browse-cats"></div>
<div id="browseGrid" class="browse-grid"></div>
</div>
<div class="map-section">
<div class="map-head" id="mapToggle"><span id="mapCaret">βΎ</span> Flavor-space map
<span class="mapsub">β every labeled molecule embedded by structure; see where structure predicts flavor</span></div>
<div id="mapWrap">
<div class="map-controls">
<span class="map-toggle3d"><button type="button" id="mapLsim" class="st-tab on">similarity</button><button type="button" id="mapLprop" class="st-tab">property axes</button></span>
<span class="map-toggle3d"><button type="button" id="map2d" class="st-tab on">2D</button><button type="button" id="map3d" class="st-tab">3D</button></span>
<span class="map-toggle3d"><button type="button" id="mapCT" class="st-tab on">taste</button><button type="button" id="mapCA" class="st-tab">aroma</button><button type="button" id="mapCB" class="st-tab">both</button></span>
<span id="mapLegend" class="map-legend"></span>
<button type="button" id="mapLabels" class="st-tab on">labels</button>
<button type="button" id="mapClusters" class="st-tab on" title="Soft density shading under each class β spotlight a class in the legend to see where it concentrates">clusters</button>
<button type="button" id="mapSpin" class="st-tab">spin</button>
<button type="button" id="mapReset" class="map-reset">Reset view</button>
</div>
<div class="map-canvas-wrap"><canvas id="flavorMap" width="1440" height="760"></canvas>
<div id="mapLoad" class="load-overlay"><div class="load-ring"></div>
<div class="load-lbl">Charting the flavor mapβ¦</div>
<div class="load-sub">placing every molecule in flavor space</div></div>
<div id="mapTip" class="map-tip" style="display:none"></div></div>
<div id="mapAxisKey" class="map-axis-key"></div>
<div class="map-key">Each dot is a molecule; <b>nearby dots are structurally similar</b>.
Color = <b>dominant taste</b> (toggle to <b>aroma</b>); tight groups are structural families.
<b>Click a class in the legend</b> to spotlight it (the rest dim). <b>2D:</b> scroll to zoom
into the cursor, drag to pan. <b>3D:</b> drag to rotate (fling it β it coasts), scroll to
zoom into the cursor. Hover for the molecule + its MW/logP/TPSA; click to open its read.</div>
<details class="map-method" open>
<summary>How it's built & how to read it</summary>
<div class="map-method-body">
<p><b>Method.</b> Each molecule becomes a 2048-bit <b>Morgan fingerprint</b> (radius 2 β
a bit for every local atom environment). Distance between two molecules is
<b>Jaccard = Tanimoto</b> on those bits, the standard chemical-similarity measure. That
high-dimensional space is projected to 2D/3D with <b>UMAP</b>, which preserves each
molecule's <i>local neighborhood</i> β so dots that sit together really are built from
the same substructures. The layout is deterministic (fixed seed).</p>
<p><b>How to read it.</b> The <i>positions</i> come purely from <b>structure</b>; the
<i>colors</i> are a flavor label laid on top. Where a color forms a clean island,
structure alone predicts that flavor (e.g. sugar-like polyols cluster sweet; alkaloid
scaffolds cluster bitter). The interesting molecules are the <b>off-color dots inside
another cluster</b> β a bitter point sitting among sweeteners is a structure that breaks
the trend, exactly the kind of lead worth a closer look.</p>
<p><b>Two flavor dimensions.</b> Flip the color toggle between <b>taste</b> (sweet /
bitter / umami / sour / salty / tasteless) and <b>aroma</b> (= odor β the same thing;
the 164 documented descriptor classes like citrus, floral, sulfurous). Same molecules,
same structural layout β you're overlaying a different sense. The <b>both</b> toggle merges
them into one full-flavor view: each dot takes its <b>taste</b> color where a taste is known,
otherwise its <b>aroma</b> color β so nearly every molecule is colored by its dominant known
flavor property. Comparing taste-only vs aroma-only vs both shows where the two senses align
(a sweet-tasting molecule that also reads floral) versus diverge.</p>
<p><b>Honest caveats β and the fix.</b> The <i>similarity</i> (UMAP) layout has axes with
<b>no units</b>; only local distances mean anything. When you want <b>real, labelled
axes</b>, switch the layout toggle to <b>property axes</b>: the same dots replotted on
<b>molecular weight</b> (x, in daltons) vs <b>logP</b> (y β hydrophobicity, oil-vs-water
partitioning), with gridlines and a logP = 0 waterline. That view <i>is</i> a metric
space β you can read a molecule's size and greasiness straight off the axes, and watch a
taste/aroma color track (or not track) those physical properties. In <b>3D</b>, property
axes add a real third dimension: <b>TPSA</b> (topological polar surface area β polarity /
H-bonding), so it's MW Γ logP Γ TPSA. Two lenses on the same molecules:
<b>similarity</b> for neighbors & outliers, <b>property axes</b> for physical structure.</p>
</div>
</details>
</div>
</div>
<div class="design-section" id="enrich">
<div class="design-head" id="enrichToggle"><span id="enrichCaret">βΎ</span> Molecule enrichment table
<span class="mapsub">β the whole universe in one grid: every molecule with its taste, aroma, food-safety, and chemistry. Sort any column; hover a header for what it means.</span></div>
<div id="enrichWrap">
<div class="enrich-controls">
<input id="enrichSearch" class="enrich-search" placeholder="Search name, SMILES, taste, aromaβ¦" autocomplete="off">
<span id="enrichCount" class="enrich-count"></span>
</div>
<div class="enrich-scroll"><table id="enrichTable" class="enrich-table"><thead id="enrichHead"></thead><tbody id="enrichBody"></tbody></table></div>
<div id="enrichPager" class="design-pager"></div>
</div>
</div>
<div id="molModal" class="mol-modal" style="display:none">
<div class="mol-modal-inner">
<div class="scroll-rod scroll-rod-top" aria-hidden="true"></div>
<div class="scroll-rod scroll-rod-bottom" aria-hidden="true"></div>
<div class="mol-actions" id="molActions">
<button type="button" id="molExport" class="mol-share" title="Download a shareable flavor card (PNG)">Export</button>
<button type="button" id="molShare" class="mol-share" title="Copy a shareable link to this read">Copy link</button>
</div>
<button type="button" id="molClose" class="mol-close" aria-label="Close">β</button>
<div id="molLoader" class="mol-loader">
<svg class="loader-flask" viewBox="0 0 140 140" aria-hidden="true">
<defs>
<linearGradient id="lg-brand" x1="0" y1="0" x2="1" y2="1">