Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Gemstone Identification & Grading Report</title>
- <meta name="description" content="Professional gemstone identification and grading report generator with automated assessment calculations, photo documentation, and PDF export.">
- <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><polygon points='50,5 85,30 100,50 85,70 50,95 15,70 0,50 15,30' fill='%23B8963E'/></svg>">
- <!-- html2pdf for Direct PDF Export -->
- <script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
- <script>
- if (typeof html2pdf === "undefined") {
- document.write('<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/html2pdf.bundle.min.js"><\/script>');
- }
- </script>
- <link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,600;0,700;1,400&family=DM+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
- <style>@font-face { font-family:'Times New Roman'; src:local('Times New Roman'), local('Times'), local('Georgia'); } </style>
- <style>
- :root {
- --navy: #111D35; --navy-med: #1B2D50; --navy-light: #2A4270; --navy-fade: #3A5585;
- --gold: #B8963E; --gold-light: #D4B565; --gold-pale: #F0E6C8;
- --cream: #FFFDF5; --warm-white: #FAFAF7; --light-gray: #E8E8E5;
- --mid-gray: #9A9A97; --dark-gray: #4A4A48; --field-line: #C0C0BD; --accent-blue: #8899BB;
- --font-display: 'Cormorant Garamond', Georgia, 'Times New Roman', serif;
- --font-body: 'Times New Roman', Times, Georgia, serif;
- }
- *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
- body { background:#2C2C30; min-height:100vh; font-family:var(--font-body); }
- .toolbar { position:sticky; top:0; z-index:1000; background:linear-gradient(135deg,var(--navy),var(--navy-med)); padding:12px 24px; display:flex; align-items:center; justify-content:space-between; box-shadow:0 4px 20px rgba(0,0,0,0.3); border-bottom:2px solid var(--gold); }
- .toolbar-left { display:flex; align-items:center; gap:14px; }
- .toolbar-logo { width:28px; height:28px; background:var(--gold); clip-path:polygon(50% 0%,85% 30%,100% 50%,85% 70%,50% 100%,15% 70%,0% 50%,15% 30%); }
- .toolbar-title { font-family:var(--font-display); color:var(--gold-light); font-size:18px; font-weight:600; }
- .toolbar-right { display:flex; gap:10px; }
- .toolbar-btn { font-family:var(--font-body); font-size:13px; font-weight:600; padding:8px 18px; border:none; border-radius:4px; cursor:pointer; transition:all 0.2s; display:flex; align-items:center; gap:6px; }
- .btn-new { background:transparent; color:var(--accent-blue); border:1px solid var(--accent-blue); }
- .btn-new:hover { background:rgba(136,153,187,0.15); }
- .btn-compare { background:transparent; color:var(--gold-light); border:1px solid var(--gold-light); }
- .btn-compare:hover { background:rgba(184,150,62,0.15); }
- .btn-pdf { background:linear-gradient(135deg, #e8edf2, #d5dce5); color:var(--navy); }
- .btn-pdf:hover { filter:brightness(1.05); transform:translateY(-1px); }
- .btn-print { background:linear-gradient(135deg,var(--gold),var(--gold-light)); color:var(--navy); }
- .btn-print:hover { filter:brightness(1.1); transform:translateY(-1px); }
- .btn-help { background:transparent; color:var(--accent-blue); border:1px solid var(--accent-blue); }
- .btn-help:hover { background:rgba(136,153,187,0.15); }
- .page-wrapper { display:flex; flex-direction:column; align-items:center; padding:30px 20px 60px; gap:40px; }
- .report-page { width:8.5in; min-height:11in; max-height:11in; background:var(--warm-white); position:relative; box-shadow:0 8px 40px rgba(0,0,0,0.4); overflow:hidden; }
- .report-page::before { content:''; position:absolute; inset:10px; border:1.5px solid var(--gold); pointer-events:none; z-index:2; }
- .report-page::after { content:''; position:absolute; inset:13px; border:0.5px solid var(--navy); pointer-events:none; z-index:2; }
- .report-inner { padding:16px 14px 14px; height:100%; }
- .report-header { background:linear-gradient(180deg,var(--navy),var(--navy-med)); padding:14px 20px 12px; text-align:center; border-bottom:2px solid var(--gold); margin-bottom:2px; position:relative; }
- .report-header::after { content:''; position:absolute; bottom:-4px; left:0; right:0; height:0.5px; background:var(--navy); }
- .report-title { font-family:var(--font-display); font-size:17px; font-weight:700; color:var(--gold-light); letter-spacing:1.5px; margin-bottom:0; }
- .header-gradient-line { height:1px; margin:4px auto; width:80%; background:linear-gradient(90deg, #C0C0BD, #4A4A48); }
- .report-subtitle { font-family:var(--font-display); font-size:16px; font-weight:700; color:var(--accent-blue); font-style:italic; }
- .report-subtitle::before,.report-subtitle::after { content:'◆'; color:var(--gold); font-size:7px; margin:0 8px; vertical-align:middle; font-style:normal; }
- .header-meta { display:flex; justify-content:space-between; align-items:center; margin-top:8px; padding-top:6px; }
- .header-meta-group { display:flex; align-items:center; gap:6px; }
- .meta-label { font-size:9px; font-weight:700; color:var(--gold-pale); letter-spacing:0.5px; text-rendering:optimizeLegibility; }
- .header-meta-group:first-child .meta-label,
- .header-meta-group:first-child .meta-value {
- font-family: 'Times New Roman', Times, serif;
- font-size: 11px;
- font-weight: 700;
- color: var(--gold-pale);
- text-rendering: optimizeLegibility;
- }
- .header-meta-group:last-child .meta-label,
- .header-meta-group:last-child .meta-input {
- font-family: 'Times New Roman', Times, serif;
- font-size: 11px;
- font-weight: 700;
- text-rendering: optimizeLegibility;
- }
- .meta-value { font-size:11px; color:var(--gold-pale); font-weight:600; text-rendering:optimizeLegibility; }
- .meta-input { font-size:10px; color:white; background:transparent; border:none; border-bottom:1px solid var(--navy-fade); padding:2px 4px; width:130px; outline:none; }
- .meta-input:focus { border-bottom-color:var(--gold); }
- .regen-btn { background:none; border:none; color:var(--accent-blue); cursor:pointer; font-size:12px; padding:2px; opacity:0.6; transition:opacity 0.2s; }
- .regen-btn:hover { opacity:1; }
- .content-columns { display:grid; grid-template-columns:40% 1fr; gap:6px; padding:3px 2px; position:relative; overflow:clip; }
- .content-columns::after { content:''; position:absolute; left:calc(40% + 3px); top:0; bottom:0; width:0.5px; background:var(--light-gray); }
- .left-col { overflow:clip; min-width:0; }
- .left-col .field-input { max-width:100%; min-width:0; width:0; flex:1 1 0; }
- .left-col .field-triple-row, .left-col .field-triple { overflow:clip; }
- .left-col .field-row { overflow:clip; min-width:0; }
- .right-col { overflow:clip; min-width:0; padding-right:2px; display:flex; flex-direction:column; }
- .right-col .scale-group { margin:0; }
- .right-col .sub-header { margin:0; padding-top:1px; margin-bottom:0; border-top:none; }
- .right-col .scale-labels { margin-top:0; }
- input[list]::-webkit-calendar-picker-indicator { display:none !important; }
- .prop-measure { font-size:13px; font-weight:700; font-family:var(--font-body); color:var(--navy); background:transparent; border:none; border-bottom:1px solid var(--field-line); padding:0 2px; outline:none; text-align:center; width:100%; }
- .prop-measure:focus { border-bottom-color:var(--gold); background:rgba(184,150,62,0.04); }
- .prop-measure::placeholder { color:var(--mid-gray); font-weight:400; font-size:10px; }
- .prop-grid { display:grid; grid-template-columns:1fr 1fr 1fr 1fr 1fr; gap:1px 4px; margin-top:2px; }
- .prop-cell { display:flex; flex-direction:column; align-items:center; }
- .prop-cell-label { font-size:12px; font-weight:600; color:var(--dark-gray); letter-spacing:0.3px; white-space:nowrap; }
- .right-col .field-input { max-width:100%; min-width:0; width:0; flex:1 1 0; }
- .right-col .field-pair, .right-col .field-triple-row { overflow:clip; }
- .right-col .field-row { overflow:clip; min-width:0; }
- .section-hdr { background:var(--navy); color:white; font-family:var(--font-display); font-size:9.5px; font-weight:700; letter-spacing:0.5px; padding:4px 8px 3px; border-radius:2px; border-bottom:1px solid var(--gold); margin-bottom:4px; margin-top:5px; text-align:center; position:relative; }
- .section-hdr.alt { background:var(--navy-med); }
- .section-hdr.alt2 { background:var(--navy-light); }
- .section-hdr:first-child { margin-top:0; }
- h2.section-hdr, h3.section-hdr { margin:0; font-size:inherit; line-height:normal; }
- .field-row { display:flex; align-items:baseline; margin-bottom:2.5px; gap:4px; }
- .field-label { font-size:8.5px; font-weight:600; color:var(--dark-gray); white-space:nowrap; flex-shrink:0; display:flex; align-items:center; }
- .field-input { flex:1; font-size:9.5px; color:var(--navy); background:transparent; border:none; border-bottom:1px solid var(--field-line); padding:2px 3px; outline:none; min-width:0; font-family:var(--font-body); }
- .field-input:focus { border-bottom-color:var(--gold); background:rgba(184,150,62,0.04); }
- .field-pair { display:grid; grid-template-columns:1fr 1fr; gap:6px; }
- .field-triple { display:grid; grid-template-columns:1fr 1fr 0.8fr; gap:4px; }
- .field-triple.sg-ri-biref { grid-template-columns:0.36fr 0.36fr 0.29fr; gap:3px; }
- .field-triple-row { display:grid; grid-template-columns:1fr 1fr 1fr; gap:4px; }
- .field-quad-row { display:grid; grid-template-columns:1fr 1fr 1fr 1fr; gap:4px; overflow:clip; }
- .field-quad-opchar { display:grid; grid-template-columns:1.2fr 0.6fr 0.6fr 0.6fr; gap:3px; overflow:clip; }
- .field-quad-grading { display:grid; grid-template-columns:0.6fr 1.5fr 0.9fr 1fr; gap:4px; overflow:clip; }
- .field-quad-color { display:grid; grid-template-columns:1.1fr 1.1fr 0.8fr 0.8fr; gap:4px; overflow:clip; }
- .field-triple-clarity { display:grid; grid-template-columns:1.2fr 1fr 1fr; gap:4px; overflow:clip; }
- .inline-btn { background:none; border:none; cursor:pointer; font-size:8px; margin-left:1px; filter:grayscale(1); transition:all 0.2s; opacity:0.7; }
- .inline-btn:hover { filter:grayscale(0); opacity:1; transform:scale(1.1); }
- .photo-area { border:1px solid var(--gold); position:relative; cursor:pointer; overflow:hidden; transition:border-color 0.2s; background:var(--warm-white); }
- .photo-area:hover { border-color:var(--gold-light); }
- .photo-area.has-image { cursor:default; }
- .photo-main { height:125px; }
- .photo-micro { height:95px; }
- .photo-spectro { height:90px; }
- .photo-grid { display:grid; grid-template-columns:1fr 1fr; gap:5px; margin:2px 0; }
- .photo-area::before,.photo-area::after { content:''; position:absolute; width:8px; height:8px; border-color:var(--gold-light); border-style:solid; pointer-events:none; z-index:1; }
- .photo-area::before { top:3px; left:3px; border-width:1px 0 0 1px; }
- .photo-area::after { top:3px; right:3px; border-width:1px 1px 0 0; }
- .photo-corner-bl,.photo-corner-br { position:absolute; width:8px; height:8px; border-color:var(--gold-light); border-style:solid; pointer-events:none; z-index:1; }
- .photo-corner-bl { bottom:3px; left:3px; border-width:0 0 1px 1px; }
- .photo-corner-br { bottom:3px; right:3px; border-width:0 1px 1px 0; }
- .photo-placeholder { display:flex; flex-direction:column; align-items:center; justify-content:center; height:100%; color:var(--mid-gray); gap:2px; }
- .photo-placeholder-icon { font-size:18px; opacity:0.4; }
- .photo-placeholder-text { font-size:8px; text-transform:uppercase; letter-spacing:0.5px; font-weight:600; }
- .photo-placeholder-sub { font-size:7.5px; font-style:italic; opacity:0.7; }
- .photo-preview { width:100%; height:100%; object-fit:contain; display:none; background:white; }
- .photo-area.has-image .photo-placeholder { display:none; }
- .photo-area.has-image .photo-preview { display:block; }
- .photo-remove { position:absolute; top:4px; right:4px; width:16px; height:16px; background:rgba(0,0,0,0.6); color:white; border:none; border-radius:50%; font-size:10px; cursor:pointer; display:none; align-items:center; justify-content:center; z-index:10; line-height:1; }
- .photo-area.has-image:hover .photo-remove { background:rgba(180,0,0,0.8); }
- .photo-area.has-image .photo-remove { display:flex; }
- input[type="file"].photo-file-input { display:none; }
- .photo-area.drag-over { border-color:var(--gold-light); background:rgba(184,150,62,0.08); border-style:dashed; }
- /* Clarity diagram inclusion plotting */
- .clar-svg-wrapper { position:relative; display:inline-block; cursor:crosshair; }
- .clar-svg-wrapper svg { display:block; }
- .inclusion-mark { position:absolute; width:6px; height:6px; background:#c00; border-radius:50%; transform:translate(-50%,-50%); pointer-events:none; z-index:5; }
- .clar-controls { display:flex; gap:3px; justify-content:center; margin-top:2px; }
- .clar-controls button { font-size:7px; padding:1px 4px; background:var(--navy); color:white; border:none; border-radius:2px; cursor:pointer; }
- .clar-controls button:hover { background:var(--gold); }
- /* Plotting Canvas CSS */
- .plot-canvas { position:absolute; inset:0; width:100%; height:100%; pointer-events:none; z-index:5; cursor:crosshair; }
- .plot-controls { position:absolute; bottom:4px; left:50%; transform:translateX(-50%); display:flex; gap:4px; z-index:15; background:rgba(255,255,255,0.85); padding:2px 4px; border-radius:4px; box-shadow:0 1px 4px rgba(0,0,0,0.2); }
- .plot-btn { background:none; border:none; font-size:10px; cursor:pointer; color:var(--navy); border-radius:2px; padding:2px 4px; transition:all 0.2s; }
- .plot-btn:hover { background:var(--light-gray); }
- .plot-btn.active { background:var(--gold); color:white; }
- .scale-group { margin:0; }
- .scale-title { font-size:8px; color:var(--navy); font-weight:700; margin-bottom:1px; text-align:center; letter-spacing:0.3px; border-top:0.5px solid var(--gold-light); padding-top:2px; }
- .scale-boxes { display:flex; gap:1px; }
- .scale-box { flex:1; height:13px; border:0.5px solid var(--mid-gray); border-radius:2px; display:flex; align-items:center; justify-content:center; font-size:6.5px; font-weight:700; color:var(--dark-gray); cursor:pointer; transition:all 0.15s; user-select:none; min-width:0; }
- .scale-box:hover, .scale-box:focus { border-color:var(--gold); transform:scale(1.05); outline:2px solid var(--gold); outline-offset:1px; }
- .scale-box.selected { background:var(--gold)!important; color:white!important; border-color:var(--gold)!important; box-shadow:0 1px 4px rgba(184,150,62,0.4); }
- .scale-labels { display:flex; justify-content:space-between; font-size:7px; color:var(--mid-gray); margin-top:0; padding:0 1px; }
- .sub-header { border-top:0.5px solid var(--gold-light); padding-top:3px; margin-top:3px; margin-bottom:3px; }
- .sub-header-text { font-size:8px; font-weight:700; color:var(--navy); letter-spacing:0.5px; text-align:center; }
- .assessment-section { margin:0 4px; border:1.5px solid var(--gold); border-radius:3px; overflow:hidden; }
- .assessment-header { background:linear-gradient(135deg,var(--navy),var(--navy-med)); padding:4px 10px; display:flex; align-items:center; justify-content:space-between; border-bottom:1.5px solid var(--gold); }
- .assessment-header-title { font-family:var(--font-display); font-size:12px; font-weight:700; color:var(--gold-light); letter-spacing:0.5px; }
- .assessment-header-auto { font-size:10px; color:var(--accent-blue); font-style:italic; font-weight:700; }
- .assessment-body { display:grid; grid-template-columns:1fr auto; gap:0; min-height:50px; }
- .assessment-breakdown { padding:5px 10px; display:grid; grid-template-columns:1fr 1fr; gap:1px 12px; }
- .assess-item { display:flex; justify-content:space-between; align-items:center; font-size:9.5px; color:var(--dark-gray); padding:1px 0; }
- .assess-item-label { font-weight:600; }
- .assess-item-value { font-weight:700; color:var(--navy); min-width:50px; text-align:right; }
- .assess-item-value.empty { color:var(--mid-gray); font-style:italic; font-weight:400; }
- .assessment-grade-box { display:flex; flex-direction:column; align-items:center; justify-content:center; padding:6px 14px; background:var(--navy); border-left:1.5px solid var(--gold); min-width:100px; transition:background 0.4s ease; }
- .grade-label { font-size:10px; color:rgba(255,255,255,0.7); letter-spacing:0.8px; margin-bottom:2px; }
- .grade-value { font-family:var(--font-display); font-size:24px; font-weight:700; transition:color 0.3s; color:white; }
- .grade-descriptor { font-size:9px; font-weight:600; letter-spacing:0.5px; margin-top:1px; transition:color 0.3s; color:white; }
- .grade-empty .grade-value, .grade-empty .grade-descriptor { color:var(--navy-fade); }
- .bottom-section { padding:0 4px; }
- .comments-area { width:100%; font-family:var(--font-body); font-size:9.5px; color:var(--navy); border:none; border-bottom:1px solid var(--field-line); background:transparent; resize:none; outline:none; padding:3px; margin-bottom:1px; }
- .comments-area:focus { border-bottom-color:var(--gold); background:rgba(184,150,62,0.03); }
- .report-footer-disclaimer { padding:3px 8px 2px; border-top:1px solid var(--gold); margin-top:0; }
- .disclaimer-text { font-size:10px; color:var(--mid-gray); line-height:1.4; margin-bottom:1px; }
- .sig-row { display:grid; grid-template-columns:1fr 1fr; gap:20px; margin-top:3px; padding-bottom:3px; }
- .sig-field { display:flex; align-items:baseline; gap:4px; }
- .sig-label { font-size:10px; font-weight:700; color:var(--dark-gray); white-space:nowrap; }
- .sig-input { flex:1; font-family:var(--font-body); font-size:11.5px; color:var(--navy); background:transparent; border:none; border-bottom:1px solid var(--field-line); padding:2px 3px; outline:none; }
- .sig-input:focus { border-bottom-color:var(--gold); }
- .report-footer-band { background:var(--navy); padding:3px 12px; display:flex; align-items:center; justify-content:center; gap:8px; border-top:1px solid var(--gold); }
- .footer-diamond { width:6px; height:6px; background:var(--gold); clip-path:polygon(50% 0%,85% 50%,50% 100%,15% 50%); flex-shrink:0; }
- .footer-text { font-size:10px; color:var(--accent-blue); letter-spacing:0.3px; font-weight:700; }
- /* PAGE 2 */
- .ref-inner { padding:14px 20px 12px; height:100%; }
- .ref-header { background:linear-gradient(180deg,var(--navy),var(--navy-med)); padding:12px 20px 10px; text-align:center; border-bottom:2px solid var(--gold); margin-bottom:6px; }
- .ref-title { font-family:var(--font-display); font-size:16px; font-weight:700; color:var(--gold-light); letter-spacing:1.5px; margin-bottom:0; }
- .ref-subtitle { font-family:var(--font-display); font-size:15px; font-weight:700; color:var(--accent-blue); font-style:italic; margin-top:0; }
- .ref-columns { display:grid; grid-template-columns:1fr 1fr; gap:12px; }
- .ref-section { margin-bottom:2px; }
- .ref-section-title { background:var(--navy); color:white; font-family:var(--font-display); font-size:9.5px; font-weight:700; letter-spacing:0.5px; padding:3px 7px; border-radius:2px; border-bottom:1px solid var(--gold); margin-bottom:1px; text-align:center; }
- .ref-section-title.alt { background:var(--navy-med); }
- /* Updated Condensed Spacing — Times New Roman, +1pt line spacing */
- .ref-entry { margin-bottom:5.5px; line-height:9.2px; text-align:justify; font-family:'Times New Roman', Times, Georgia, serif; }
- .ref-section.ref-compact .ref-entry { margin-bottom:0.35px; }
- .ref-term { font-size:9.2px; font-weight:700; color:var(--navy); font-family:'Times New Roman', Times, Georgia, serif; }
- .ref-def { font-size:8.8px; color:var(--dark-gray); font-family:'Times New Roman', Times, Georgia, serif; }
- .ref-table { width:100%; border-collapse:collapse; margin:2px 0 3px; font-size:8.8px; font-family:'Times New Roman', Times, Georgia, serif; }
- .ref-table th { background:var(--navy); color:var(--gold-pale); padding:2px 4px; text-align:left; font-weight:600; font-size:7.5px; letter-spacing:0.3px; text-transform:uppercase; font-family:'Times New Roman', Times, Georgia, serif; }
- .ref-table td { padding:1.5px 4px; border-bottom:0.5px solid var(--light-gray); color:var(--dark-gray); line-height:9.2px; }
- .ref-table tr:nth-child(even) td { background:rgba(0,0,0,0.02); }
- .ref-table .grade-num { font-weight:700; color:var(--navy); text-align:center; width:24px; }
- .ref-divider { border:none; border-top:0.5px solid var(--gold-light); margin:3px 0; }
- .ref-full-width { grid-column:1/-1; }
- .ref-section + .ref-section { margin-top: 10px; }
- .pdf-na { font-size:9px; font-style:italic; color:var(--mid-gray); border-bottom:1px solid var(--field-line); padding:2px 3px; flex:1; font-family:var(--font-body); }
- /* PDF EXPORT / PRINT MODIFIERS */
- .exporting .toolbar { display:none !important; }
- .exporting .page-wrapper { padding:0 !important; gap:0 !important; }
- body.exporting { background:white !important; }
- .exporting .regen-btn, .exporting .photo-remove, .exporting .plot-controls, .exporting .inline-btn { display:none !important; }
- .exporting .field-input, .exporting .meta-input, .exporting .comments-area, .exporting .sig-input { border-bottom-color:transparent !important; background:transparent !important; }
- @media print {
- @page { size:letter portrait; margin:0; }
- body { background:white!important; -webkit-print-color-adjust:exact; print-color-adjust:exact; }
- .toolbar { display:none!important; }
- .page-wrapper { padding:0!important; gap:0!important; }
- .report-page { box-shadow:none!important; page-break-after:always; }
- .report-page:last-child { page-break-after:avoid; }
- .field-input,.meta-input,.comments-area,.sig-input { border-bottom-color:transparent!important; background:transparent!important; }
- .regen-btn, .photo-remove, .plot-controls, .inline-btn { display:none!important; }
- .photo-area { cursor:default!important; }
- .report-header,.section-hdr,.report-footer-band,.scale-box,.scale-box.selected,.assessment-header,.assessment-grade-box,.ref-header,.ref-section-title,.ref-table th { -webkit-print-color-adjust:exact; print-color-adjust:exact; }
- }
- /* Modals */
- .modal-overlay { display:none; position:fixed; inset:0; background:rgba(0,0,0,0.6); z-index:2000; align-items:center; justify-content:center; }
- .modal-overlay.active { display:flex; }
- .modal { background:white; border-radius:8px; padding:28px 32px; max-width:560px; max-height:80vh; overflow-y:auto; box-shadow:0 20px 60px rgba(0,0,0,0.3); border-top:3px solid var(--gold); }
- .modal.large-modal { max-width:850px; width:90%; padding:24px; }
- .modal h2 { font-family:var(--font-display); color:var(--navy); font-size:20px; margin-bottom:14px; }
- .modal h3 { font-family:var(--font-display); color:var(--navy); font-size:16px; margin-bottom:10px; border-bottom:1px solid var(--gold); padding-bottom:4px; }
- .modal p,.modal li { font-family:var(--font-body); font-size:13px; color:var(--dark-gray); line-height:1.6; margin-bottom:8px; }
- .modal ul { padding-left:20px; margin-bottom:12px; }
- .modal .modal-close { margin-top:16px; padding:8px 24px; background:var(--navy); color:white; border:none; border-radius:4px; cursor:pointer; font-family:var(--font-body); font-weight:600; margin-right:8px; }
- /* Color Picker Custom Styles */
- .cp-row { margin-bottom: 15px; }
- .cp-row label { display:block; font-size:13px; font-weight:600; color:var(--navy); margin-bottom:5px; }
- .cp-row span { color:var(--accent-blue); font-weight:400; }
- .cp-slider { width:100%; cursor:pointer; }
- #cp-preview { height:70px; border-radius:6px; margin:20px 0 10px; border:1px solid var(--mid-gray); box-shadow:inset 0 2px 10px rgba(0,0,0,0.1); transition:background 0.2s; }
- .cp-presets { margin: 15px 0; }
- .cp-preset { padding:6px 10px; background:var(--navy); color:white; border:1px solid var(--gold); border-radius:4px; cursor:pointer; font-family:var(--font-body); font-size:11px; font-weight:600; transition:all 0.2s; }
- .cp-preset:hover { background:var(--navy-med); transform:translateY(-1px); box-shadow:0 2px 8px rgba(184,150,62,0.3); }
- /* Comparison Chart Styles */
- .compare-container { display:flex; gap:24px; }
- .compare-left { flex: 1; display:flex; flex-direction:column; }
- .compare-right { flex: 1; overflow-y:auto; padding-right:8px; display:flex; flex-direction:column; gap:16px; }
- #compare-img-box { flex-grow:1; background:#f4f4f4; border:1px solid #ddd; border-radius:4px; display:flex; align-items:center; justify-content:center; overflow:hidden; min-height:300px; }
- #compare-img { max-width:100%; max-height:100%; object-fit:contain; }
- .chart-box { border:1px solid var(--light-gray); border-radius:4px; padding:10px; background:#fafafa; }
- .chart-title { font-size:11px; font-weight:700; color:var(--navy); margin-bottom:6px; text-transform:uppercase; letter-spacing:0.5px; }
- .tone-scale { display:flex; height:24px; width:100%; border-radius:3px; overflow:hidden; border:1px solid var(--mid-gray); }
- .tone-block { flex:1; }
- .sat-scale { display:flex; height:24px; width:100%; border-radius:3px; overflow:hidden; border:1px solid var(--mid-gray); }
- .sat-block { flex:1; }
- .clarity-list { display:flex; flex-direction:column; gap:4px; }
- .clarity-item { display:flex; align-items:center; font-size:11px; color:var(--dark-gray); }
- .clarity-badge { background:var(--navy); color:white; font-weight:700; width:30px; text-align:center; border-radius:3px; margin-right:8px; padding:2px 0; font-size:10px; }
- .clarity-desc { flex:1; }
- #fluo-warning { cursor:help; font-size:10px; padding:1px 4px; border-radius:2px; background:rgba(255,200,0,0.2); border:1px solid #ffcc00; display:none; position:absolute; right:8px; top:3px; }
- #inp-grp,#inp-spec,#inp-var,#inp-orig,#inp-crystal,#inp-opchar,#inp-sg,#inp-ri,#inp-biref,
- #inp-cq,#inp-sym,#inp-prof,#inp-pol,#inp-fup,#inp-bril,#inp-win,#inp-ext { font-size:10.5px; }
- [for="inp-grp"],[for="inp-spec"],[for="inp-var"],[for="inp-orig"],[for="inp-crystal"],[for="inp-opchar"],[for="inp-sg"],[for="inp-ri"],[for="inp-biref"],
- [for="inp-cq"],[for="inp-sym"],[for="inp-prof"],[for="inp-pol"],[for="inp-fup"],[for="inp-bril"],[for="inp-win"],[for="inp-ext"] { font-size:9.5px; }
- .sg-out-of-range { background:rgba(255,80,80,0.15) !important; border-bottom-color:#c44 !important; }
- </style>
- </head>
- <body>
- <datalist id="variety-list">
- <option value="Ruby"></option><option value="Sapphire"></option><option value="Emerald"></option>
- <option value="Aquamarine"></option><option value="Morganite"></option><option value="Alexandrite"></option>
- <option value="Tsavorite"></option><option value="Spessartite"></option><option value="Rhodolite"></option>
- <option value="Garnet"></option><option value="Amethyst"></option><option value="Citrine"></option>
- <option value="Tanzanite"></option><option value="Topaz"></option><option value="Tourmaline"></option>
- <option value="Paraiba"></option><option value="Spinel"></option><option value="Peridot"></option>
- <option value="Zircon"></option><option value="Diamond"></option>
- </datalist>
- <datalist id="spec-list">
- <option value="Corundum"></option><option value="Beryl"></option><option value="Chrysoberyl"></option>
- <option value="Grossular"></option><option value="Spessartine"></option><option value="Pyrope-Almandine"></option>
- <option value="Pyrope"></option><option value="Almandine"></option><option value="Andradite"></option>
- <option value="Uvarovite"></option><option value="Elbaite"></option><option value="Dravite"></option>
- <option value="Liddicoatite"></option><option value="Schorl"></option><option value="Uvite"></option>
- <option value="Quartz"></option><option value="Zoisite"></option><option value="Topaz"></option>
- <option value="Spinel"></option><option value="Olivine"></option><option value="Zircon"></option>
- <option value="Diamond"></option>
- </datalist>
- <datalist id="grp-list">
- <option value="Corundum"></option><option value="Beryl"></option><option value="Chrysoberyl"></option>
- <option value="Garnet"></option><option value="Tourmaline"></option><option value="Quartz"></option>
- <option value="Epidote"></option><option value="Topaz"></option><option value="Spinel"></option>
- <option value="Olivine"></option><option value="Zircon"></option><option value="Native Element"></option>
- </datalist>
- <!-- Optical Character -->
- <datalist id="opchar-list">
- <option value="SR (Singly Refractive / Isotropic)"></option>
- <option value="DR Uniaxial (+)"></option><option value="DR Uniaxial (−)"></option>
- <option value="DR Biaxial (+)"></option><option value="DR Biaxial (−)"></option>
- <option value="AGG (Aggregate)"></option><option value="ADR (Anomalous DR)"></option>
- </datalist>
- <!-- Crystal System -->
- <datalist id="crystal-list">
- <option value="Isometric (Cubic)"></option>
- <option value="Hexagonal (Trigonal)"></option><option value="Hexagonal"></option>
- <option value="Tetragonal"></option><option value="Orthorhombic"></option>
- <option value="Monoclinic"></option><option value="Triclinic"></option>
- <option value="Amorphous"></option><option value="Polycrystalline"></option>
- </datalist>
- <!-- UV Fluorescence Responses -->
- <datalist id="uv-list">
- <option value="Inert"></option><option value="VW Red"></option><option value="W Red"></option>
- <option value="Mod Red"></option><option value="St Red"></option><option value="VSt Red"></option>
- <option value="W Orange"></option><option value="Mod Orange"></option><option value="St Orange"></option>
- <option value="W Yellow"></option><option value="Mod Yellow"></option><option value="St Yellow"></option>
- <option value="W Green"></option><option value="Mod Green"></option><option value="St Green"></option>
- <option value="W Blue"></option><option value="Mod Blue"></option><option value="St Blue"></option>
- <option value="W White"></option><option value="Mod White"></option><option value="St White"></option>
- <option value="Chalky Blue"></option><option value="Chalky Green"></option><option value="Chalky Yellow"></option>
- <option value="W Pink"></option><option value="Mod Pink"></option><option value="St Pink"></option>
- <option value="W Purple"></option><option value="Mod Purple"></option>
- </datalist>
- <!-- Dichroscope Responses -->
- <datalist id="dich-list">
- <option value="None (Isotropic / SRI)"></option><option value="Weak Dichroic"></option>
- <option value="Moderate Dichroic"></option><option value="Strong Dichroic"></option>
- <option value="Trichroic (Weak)"></option><option value="Trichroic (Moderate)"></option>
- <option value="Trichroic (Strong)"></option><option value="ADR (Anomalous)"></option>
- </datalist>
- <!-- Chelsea Filter Responses -->
- <datalist id="chel-list">
- <option value="No Reaction"></option><option value="Green"></option><option value="Blue-Green"></option>
- <option value="Pink"></option><option value="Red"></option><option value="Strong Red"></option>
- <option value="Orange-Pink"></option><option value="Greenish Grey"></option>
- </datalist>
- <!-- Ruby Filter Responses -->
- <datalist id="ruby-list">
- <option value="Dark (No Reaction)"></option><option value="Faint Glow"></option>
- <option value="Moderate Glow"></option><option value="Strong Glow"></option>
- <option value="Bright Red Glow"></option><option value="Pink Glow"></option>
- </datalist>
- <!-- Shape Options -->
- <datalist id="shape-list">
- <option value="Round"></option><option value="Oval"></option><option value="Cushion"></option>
- <option value="Emerald Cut"></option><option value="Pear"></option><option value="Marquise"></option>
- <option value="Heart"></option><option value="Princess"></option><option value="Radiant"></option>
- <option value="Asscher"></option><option value="Trillion"></option><option value="Baguette"></option>
- <option value="Cabochon"></option><option value="Briolette"></option><option value="Rose Cut"></option>
- <option value="Freeform"></option><option value="Hexagonal"></option><option value="Kite"></option>
- <option value="Shield"></option><option value="Trapezoid"></option>
- </datalist>
- <!-- Cut Style Options -->
- <datalist id="cutstyle-list">
- <option value="Brilliant"></option><option value="Step"></option><option value="Mixed"></option>
- <option value="Modified Brilliant"></option><option value="Rose"></option><option value="Cabochon"></option>
- <option value="Buff Top"></option><option value="Checkerboard"></option><option value="Fantasy"></option>
- <option value="Portuguese"></option><option value="Scissor"></option><option value="Barion"></option>
- <option value="Old Mine"></option><option value="Old European"></option>
- <option value="Concave"></option><option value="Mogul"></option><option value="Millennium"></option>
- <option value="Briolette"></option><option value="Double Rose"></option><option value="Peruzzi"></option>
- <option value="Flanders"></option><option value="Radiant"></option><option value="Asscher"></option>
- <option value="Ceylon"></option><option value="Carved"></option><option value="Faceted Cab"></option>
- <option value="Tallow Top"></option><option value="Swiss"></option>
- <option value="Antique"></option><option value="French Cut"></option><option value="Hexagonal Cut"></option>
- <option value="Kite Cut"></option><option value="Lozenge"></option><option value="Mazarin"></option>
- <option value="Mirror Cut"></option><option value="Native Cut"></option><option value="Opposed Bar"></option>
- <option value="Precision Cut"></option><option value="Royal Cut"></option><option value="Shield Cut"></option>
- <option value="Star Cut"></option><option value="Table Cut"></option><option value="Trilliant"></option>
- <option value="Window Cut"></option>
- </datalist>
- <!-- Cut Quality / Symmetry / Polish / Face-up / Profile Grades -->
- <datalist id="grade5-list">
- <option value="Poor"></option><option value="Fair"></option><option value="Good"></option>
- <option value="Very Good"></option><option value="Excellent"></option>
- </datalist>
- <!-- Brilliance Percentage -->
- <datalist id="bril-list">
- <option value="< 50%"></option><option value="50 - 60%"></option><option value="60 - 70%"></option>
- <option value="70 - 80%"></option><option value="80 - 85%"></option><option value="85 - 90%"></option>
- <option value="90%+"></option>
- </datalist>
- <!-- Enhancement Standard Treatment -->
- <datalist id="treat-std-list">
- <option value="None (N)"></option><option value="Heat (H)"></option><option value="Low-Temp Heat (LTH)"></option>
- <option value="Heat with Flux (Hf)"></option><option value="Heat with Pressure (HP)"></option>
- <option value="Not Heated (NH)"></option>
- </datalist>
- <!-- Enhancement Additional Treatment -->
- <datalist id="treat-add-list">
- <option value="None"></option><option value="Oiling (O)"></option><option value="Resin Filling (RF)"></option>
- <option value="Lead-Glass Filling (LGF)"></option><option value="Beryllium Diffusion (Be)"></option>
- <option value="Lattice Diffusion (D)"></option><option value="Surface Coating (C)"></option>
- <option value="Dyeing (Dy)"></option><option value="Irradiation (R)"></option><option value="Fracture Filling (F)"></option>
- <option value="Bleaching (B)"></option><option value="HPHT"></option><option value="Waxing / Impregnation (W)"></option>
- </datalist>
- <!-- Stability Options -->
- <datalist id="stab-list">
- <option value="Excellent"></option><option value="Very Good"></option><option value="Good"></option>
- <option value="Fair"></option><option value="Poor"></option><option value="Unstable"></option>
- </datalist>
- <!-- Clarity Assessment Dropdowns -->
- <datalist id="clargrade-list">
- <option value="FI (Flawless/Internally Flawless)"></option>
- <option value="Eye Clean"></option>
- <option value="LI (Lightly Included)"></option>
- <option value="MI (Moderately Included)"></option>
- <option value="HI (Heavily Included)"></option>
- <option value="EI₂ (Excessively Included)"></option>
- <option value="EI₃ (Severely Included)"></option>
- </datalist>
- <datalist id="clartype-list">
- <option value="Type I (Usually Eye Clean)"></option>
- <option value="Type II (Usually Included)"></option>
- <option value="Type III (Almost Always Included)"></option>
- </datalist>
- <datalist id="transp-list">
- <option value="Transparent"></option>
- <option value="Semi-Transparent"></option>
- <option value="Translucent"></option>
- <option value="Semi-Translucent"></option>
- <option value="Opaque"></option>
- </datalist>
- <div class="toolbar">
- <div class="toolbar-left"><div class="toolbar-logo"></div><span class="toolbar-title">Gemstone Report Generator</span></div>
- <div class="toolbar-right">
- <button class="toolbar-btn btn-help" onclick="showHelp()" aria-label="Open help guide">❓ Help</button>
- <button class="toolbar-btn btn-compare" onclick="openCompareChart()" aria-label="Open comparison chart">⚖️ Compare Chart</button>
- <button class="toolbar-btn btn-new" onclick="newReport()" aria-label="Start new report">✦ New Report</button>
- <button class="toolbar-btn btn-pdf" onclick="exportPDF()" aria-label="Export as PDF">📄 Export PDF</button>
- <button class="toolbar-btn btn-print" onclick="window.print()" aria-label="Print report">🖨 Print</button>
- </div>
- </div>
- <div class="modal-overlay" id="helpModal"><div class="modal" style="max-height:90vh; overflow-y:auto;">
- <h2>Complete Guide to This Report</h2>
- <h3>Getting Started</h3>
- <p><strong>Auto-Save:</strong> All text, photos, and grades are automatically saved to your browser. You can close this window and your work will be here when you return.</p>
- <p><strong>Creating Reports:</strong> Click "New Report" to start a fresh gemstone assessment. Your current report is automatically saved to your browser.</p>
- <h3>Smart Database</h3>
- <p>The database recognizes 20+ common gemstone varieties and automatically populates Species and Group fields:</p>
- <ul>
- <li><strong>Variety:</strong> Start typing a known variety (e.g., "Ruby", "Sapphire", "Emerald"). The database instantly fills Species and Group.</li>
- <li><strong>Species & Group:</strong> Now searchable independently! The full list of species and groups is available via dropdown even if you haven't selected a Variety.</li>
- <li><strong>Multi-Option Species:</strong> For varieties with multiple possible species (like Garnet or Tourmaline), a dropdown appears for selection.</li>
- </ul>
- <h3>Photo Features</h3>
- <ul>
- <li><strong>Upload Methods:</strong> Click any photo area to browse, or drag-and-drop images directly.</li>
- <li><strong>Photo Areas:</strong> Main (Daylight), Magnified (10x), Microscope (30x+), and Spectro sections each hold distinct images.</li>
- <li><strong>Inclusion Plotting:</strong> Click the ✎ icon below Microscope Image 2 to overlay red inclusion marks on your image for documentation.</li>
- <li><strong>Remove Images:</strong> Hover over any uploaded photo and click the X button to delete and re-upload.</li>
- </ul>
- <h3>Grading System</h3>
- <ul>
- <li><strong>Color Grade (1-10):</strong> Assess saturation, hue purity, and tone. Use the interactive color picker (💡 button) to visualize GIA color codes.</li>
- <li><strong>Clarity Grade (1-10):</strong> Evaluate eye-visibility of inclusions. Reference charts compare your observations to standard benchmarks.</li>
- <li><strong>Cutting Grade (1-10):</strong> Consider proportions, symmetry, and polish. Reference diagrams help identify cut quality issues.</li>
- <li><strong>Finish Grade (1-10):</strong> Polish and facet alignment affect brilliance and durability.</li>
- <li><strong>Stability Index:</strong> Treatment permanence. Heated stones score high; fracture-filled stones score low.</li>
- </ul>
- <h3>Color Analysis</h3>
- <ul>
- <li><strong>Interactive Color Picker:</strong> Use the 💡 icon to open an HSL-based color selector with 19+ hues covering the full GIA color wheel.</li>
- <li><strong>Hue Slider:</strong> Select from Red, Orangy Red, Orange, Yellow, Green, Blue, Violet, Purple, Pink, Brown, Gray, and more.</li>
- <li><strong>Tone Slider:</strong> From Colorless (0) to Black (10), representing the stone's darkness.</li>
- <li><strong>Saturation Slider:</strong> From Colorless/Near Colorless (0) to Vivid (6).</li>
- <li><strong>Preset Colors:</strong> Quick-select buttons for Padparadscha, Pigeon Blood, Cornflower Blue, and Royal Blue.</li>
- </ul>
- <h3>Diagnostic Tools</h3>
- <ul>
- <li><strong>RI (Refractive Index) Auto-Fill:</strong> Entering a species auto-populates typical RI ranges and optical character.</li>
- <li><strong>SG Calculator:</strong> Click the ⚙ icon next to Specific Gravity to calculate from Carat Weight and Measurements.</li>
- <li><strong>SG Range Checker:</strong> A yellow ⚠️ warning appears if your SG falls outside the normal range for the identified species.</li>
- <li><strong>UV Anomaly Checker:</strong> Unnatural fluorescence combinations trigger alerts (e.g., red fluorescence in natural blue sapphire).</li>
- <li><strong>Auto-Formatting:</strong> Type measurement numbers like "9.5x7x5" and click away; it auto-formats to "9.50 x 7.00 x 5.00 mm".</li>
- </ul>
- <h3>PDF Export & Printing</h3>
- <ul>
- <li><strong>Direct PDF Export:</strong> Click "Export PDF" to generate a high-quality PDF suitable for archives or client delivery.</li>
- <li><strong>Print to PDF:</strong> Use your browser's Print function for custom paper sizes or binding.</li>
- <li><strong>Color Accuracy:</strong> Both methods preserve the navy/gold report styling and photo placements.</li>
- </ul>
- <h3>Keyboard Shortcuts</h3>
- <ul>
- <li><strong>Tab:</strong> Navigate between fields quickly.</li>
- <li><strong>Ctrl+P / Cmd+P:</strong> Open Print dialog directly.</li>
- <li><strong>Enter (in color picker):</strong> Apply selected color immediately.</li>
- </ul>
- <button class="modal-close" onclick="closeHelp()">Got It</button>
- </div></div>
- <div class="modal-overlay" id="colorModal"><div class="modal" style="max-width:400px;">
- <h2>Interactive Color Picker</h2>
- <div class="cp-row">
- <label for="cp-hue">Hue: <span id="cp-hue-label">Red</span></label>
- <input type="range" id="cp-hue" class="cp-slider" min="0" max="19" value="0" oninput="updateColorPreview()">
- </div>
- <div class="cp-row">
- <label>Color Modifier: <span id="cp-mod-label">None</span></label>
- <select id="cp-modifier" onchange="updateColorPreview()" style="width:100%; padding:6px; font-family:var(--font-body); font-size:12px; border:1px solid var(--field-line); border-radius:4px; color:var(--navy); cursor:pointer;">
- <option value="">None</option>
- <option value="pinkish">Pinkish</option>
- <option value="orangy">Orangy</option>
- <option value="yellowish">Yellowish</option>
- <option value="greenish">Greenish</option>
- <option value="bluish">Bluish</option>
- <option value="violetish">Violetish</option>
- <option value="brownish">Brownish</option>
- <option value="grayish">Grayish</option>
- </select>
- </div>
- <div class="cp-row">
- <label for="cp-ton">Tone: <span id="cp-ton-label">5 - Medium</span></label>
- <input type="range" id="cp-ton" class="cp-slider" min="0" max="10" value="5" oninput="updateColorPreview()">
- </div>
- <div class="cp-row">
- <label for="cp-sat">Saturation: <span id="cp-sat-label">5 - Strong</span></label>
- <input type="range" id="cp-sat" class="cp-slider" min="0" max="6" value="5" oninput="updateColorPreview()">
- </div>
- <div id="cp-preview"></div>
- <div class="cp-presets">
- <p style="font-size:11px; font-weight:600; margin-bottom:8px; color:var(--navy);">Gemstone Preset Colors:</p>
- <div style="display:grid; grid-template-columns:1fr 1fr; gap:6px;">
- <button class="cp-preset" onclick="applyPresetColor(3, 5, 5)">Padparadscha</button>
- <button class="cp-preset" onclick="applyPresetColor(0, 7, 6)">Pigeon Blood</button>
- <button class="cp-preset" onclick="applyPresetColor(11, 5, 6)">Cornflower Blue</button>
- <button class="cp-preset" onclick="applyPresetColor(11, 6, 6)">Royal Blue</button>
- </div>
- </div>
- <button class="modal-close" onclick="applyColor()">Apply Color</button>
- <button class="modal-close" style="background:var(--accent-blue)" onclick="closeColorPicker()">Cancel</button>
- </div></div>
- <!-- COMPARISON CHART MODAL -->
- <div class="modal-overlay" id="compareModal"><div class="modal large-modal">
- <h2>Interactive Assessment Comparison & Reference Charts</h2>
- <div class="compare-container">
- <div class="compare-left">
- <h3>Your Uploaded Gemstone</h3>
- <div id="compare-img-box">
- <img id="compare-img" src="" alt="No Image Uploaded">
- </div>
- <p style="font-size:10px; color:gray; text-align:center; margin-top:6px;">(Pulls directly from Photo 1 — Daylight)</p>
- <h4 style="margin-top:12px; font-size:11px; color:var(--navy); font-weight:700;">Your Current Report Data:</h4>
- <div style="background:#f5f5f5; padding:8px; border-radius:4px; font-size:10px; color:var(--dark-gray); line-height:1.6;">
- <div><strong>Variety:</strong> <span id="compare-variety">Not specified</span></div>
- <div><strong>Color Grade:</strong> <span id="compare-color">—</span></div>
- <div><strong>Clarity Grade:</strong> <span id="compare-clarity">—</span></div>
- <div><strong>Cutting Grade:</strong> <span id="compare-cutting">—</span></div>
- <div><strong>Overall Grade:</strong> <span id="compare-overall">—</span></div>
- </div>
- </div>
- <div class="compare-right">
- <h3>Industry Reference Charts</h3>
- <div class="chart-box">
- <div class="chart-title">Tone Scale Reference</div>
- <div style="font-size:9px; margin-bottom:4px; color:var(--mid-gray);">Lightness progression from Colorless to Black</div>
- <div class="tone-scale">
- <div class="tone-block" style="background: hsl(0, 0%, 99%); border-right:1px solid white;"></div>
- <div class="tone-block" style="background: hsl(0, 0%, 92%); border-right:1px solid white;"></div>
- <div class="tone-block" style="background: hsl(0, 0%, 80%); border-right:1px solid white;"></div>
- <div class="tone-block" style="background: hsl(0, 0%, 70%); border-right:1px solid white;"></div>
- <div class="tone-block" style="background: hsl(0, 0%, 55%); border-right:1px solid white;"></div>
- <div class="tone-block" style="background: hsl(0, 0%, 40%); border-right:1px solid white;"></div>
- <div class="tone-block" style="background: hsl(0, 0%, 30%); border-right:1px solid white;"></div>
- <div class="tone-block" style="background: hsl(0, 0%, 15%); border-right:1px solid white;"></div>
- <div class="tone-block" style="background: hsl(0, 0%, 8%); border-right:1px solid white;"></div>
- <div class="tone-block" style="background: hsl(0, 0%, 3%); border-right:1px solid white;"></div>
- <div class="tone-block" style="background: hsl(0, 0%, 1%);"></div>
- </div>
- <div style="display:flex; justify-content:space-between; font-size:8px; color:var(--mid-gray); margin-top:2px; padding:0 2px;">
- <span>0 Colorless</span><span>5 Medium</span><span>10 Black</span>
- </div>
- </div>
- <div class="chart-box">
- <div class="chart-title">Saturation Scale Reference</div>
- <div style="font-size:9px; margin-bottom:4px; color:var(--mid-gray);">Intensity from Colorless to Vivid (blue hue example)</div>
- <div class="sat-scale">
- <div class="sat-block" style="background: hsl(220, 0%, 50%);"></div>
- <div class="sat-block" style="background: hsl(220, 16%, 50%);"></div>
- <div class="sat-block" style="background: hsl(220, 33%, 50%);"></div>
- <div class="sat-block" style="background: hsl(220, 50%, 50%);"></div>
- <div class="sat-block" style="background: hsl(220, 66%, 50%);"></div>
- <div class="sat-block" style="background: hsl(220, 83%, 50%);"></div>
- <div class="sat-block" style="background: hsl(220, 100%, 50%);"></div>
- </div>
- <div style="display:flex; justify-content:space-between; font-size:8px; color:var(--mid-gray); margin-top:2px; padding:0 2px;">
- <span>0 Colorless</span><span>3 Mod.</span><span>6 Vivid</span>
- </div>
- </div>
- <div class="chart-box">
- <div class="chart-title">Cut Grade Reference</div>
- <div class="clarity-list">
- <div class="clarity-item">
- <div class="clarity-badge" style="background:var(--gold);">E</div>
- <div class="clarity-desc"><strong>Excellent (9-10):</strong> Perfect/near-perfect proportions, optimal light return, superior symmetry and polish.</div>
- </div>
- <div class="clarity-item">
- <div class="clarity-badge" style="background:var(--navy-light);">VG</div>
- <div class="clarity-desc"><strong>Very Good (7-8):</strong> Excellent proportions with minimal variance, strong light performance, minor polish marks.</div>
- </div>
- <div class="clarity-item">
- <div class="clarity-badge" style="background:var(--navy-fade);">G</div>
- <div class="clarity-desc"><strong>Good (5-6):</strong> Good proportions, acceptable light return, noticeable but non-critical asymmetry or polish issues.</div>
- </div>
- <div class="clarity-item">
- <div class="clarity-badge" style="background:var(--mid-gray);">F</div>
- <div class="clarity-desc"><strong>Fair (3-4):</strong> Noticeable deviation from ideal proportions, visible symmetry/polish defects, reduced brilliance.</div>
- </div>
- <div class="clarity-item">
- <div class="clarity-badge" style="background:var(--dark-gray); color:white;">P</div>
- <div class="clarity-desc"><strong>Poor (1-2):</strong> Significant proportional issues, major asymmetry/polish defects, noticeably reduced light performance.</div>
- </div>
- </div>
- </div>
- <div class="chart-box">
- <div class="chart-title">Enhancement Reference</div>
- <table style="width:100%; font-size:9px; border-collapse:collapse;">
- <tr style="background:var(--navy); color:white;">
- <th style="padding:4px; text-align:left; font-weight:700;">Treatment</th>
- <th style="padding:4px; text-align:left; font-weight:700;">Stability</th>
- </tr>
- <tr style="border-bottom:1px solid var(--light-gray);">
- <td style="padding:4px;">Heat Treatment</td>
- <td style="padding:4px;">Permanent — Industry Standard</td>
- </tr>
- <tr style="border-bottom:1px solid var(--light-gray); background:rgba(0,0,0,0.02);">
- <td style="padding:4px;">Irradiation</td>
- <td style="padding:4px;">High — Stable in normal conditions</td>
- </tr>
- <tr style="border-bottom:1px solid var(--light-gray);">
- <td style="padding:4px;">Glass Filling</td>
- <td style="padding:4px;">Variable — Can fade with heat/cleaning</td>
- </tr>
- <tr style="border-bottom:1px solid var(--light-gray); background:rgba(0,0,0,0.02);">
- <td style="padding:4px;">Oiling/Resin</td>
- <td style="padding:4px;">Variable — Requires maintenance</td>
- </tr>
- <tr>
- <td style="padding:4px;">Coating</td>
- <td style="padding:4px;">Low — Can wear with cleaning</td>
- </tr>
- </table>
- </div>
- </div>
- </div>
- <button class="modal-close" onclick="closeCompareChart()">Close</button>
- </div></div>
- <div class="page-wrapper" id="pdfExportWrapper">
- <!-- ======== PAGE 1 ======== -->
- <div class="report-page" id="page1"><div class="report-inner">
- <div class="report-header">
- <div class="report-title">GEMSTONE IDENTIFICATION & GRADING REPORT</div>
- <div class="header-gradient-line"></div>
- <div class="report-subtitle">Comprehensive Analysis Certificate</div>
- <div class="header-meta">
- <div class="header-meta-group"><span class="meta-label">Report No.</span><span class="meta-value" id="reportNum"></span><button class="regen-btn" onclick="regenerateNum()" title="Regenerate" aria-label="Regenerate report number">↻</button></div>
- <div class="header-meta-group"><label class="meta-label" for="inp-date">Date:</label><input type="date" class="meta-input" id="inp-date" style="width:140px;"></div>
- </div>
- </div>
- <div class="content-columns">
- <!-- LEFT COLUMN -->
- <div class="left-col">
- <h2 class="section-hdr">Gem Identification</h2>
- <div class="field-triple-row"><div class="field-row"><label class="field-label" for="inp-grp">Group:</label><input class="field-input" id="inp-grp" list="grp-list" placeholder="e.g. Beryl, Garnet"></div><div class="field-row"><label class="field-label" for="inp-spec">Species:</label><input class="field-input" id="inp-spec" list="spec-list" placeholder="e.g. Chrysoberyl"></div><div class="field-row"><label class="field-label" for="inp-var">Variety:</label><input class="field-input" id="inp-var" list="variety-list" placeholder="e.g. Ruby"></div></div>
- <div class="field-pair" style="grid-template-columns:0.85fr 1.15fr;"><div class="field-row"><label class="field-label" for="inp-orig">Origin:</label><input class="field-input" id="inp-orig" placeholder="e.g. Sri Lanka, Brazil"></div><div class="field-row"><label class="field-label" for="inp-crystal">Crystal System:</label><input class="field-input" id="inp-crystal" list="crystal-list" placeholder="e.g. Hexagonal"></div></div>
- <div class="field-quad-opchar"><div class="field-row"><label class="field-label" for="inp-opchar">Optic Char:</label><input class="field-input" id="inp-opchar" list="opchar-list" placeholder="e.g. DR Uniaxial (−)"></div><div class="field-row"><label class="field-label" for="inp-sg">SG:</label><button class="inline-btn" onclick="calcSG()" title="Calculate from Wt & Meas" aria-label="Calculate specific gravity" tabindex="0" role="button">⚙️</button><input class="field-input" id="inp-sg" placeholder="3.73"><span id="sg-warning" style="cursor:help;font-size:10px;padding:1px 4px;border-radius:2px;background:rgba(255,200,0,0.2);border:1px solid #ffcc00;display:none;margin-left:2px;white-space:nowrap;">⚠️</span></div><div class="field-row"><label class="field-label" for="inp-ri">RI:</label><input class="field-input" id="inp-ri" placeholder="1.544-1.553"><span id="ri-warning" style="cursor:help;font-size:10px;padding:1px 4px;border-radius:2px;background:rgba(255,200,0,0.2);border:1px solid #ffcc00;display:none;margin-left:2px;white-space:nowrap;">⚠️</span></div><div class="field-row"><label class="field-label" for="inp-biref">Biref:</label><input class="field-input" id="inp-biref" placeholder="0.009"></div></div>
- <h2 class="section-hdr">Gemstone Photographs</h2>
- <div class="photo-grid">
- <div class="photo-area photo-main" id="photoMain1" onclick="triggerUpload('fileMain1')" ondragover="dragOver(event)" ondragleave="dragLeave(event)" ondrop="dropFile(event,'photoMain1','previewMain1')">
- <div class="photo-placeholder"><div class="photo-placeholder-icon">📷</div><div class="photo-placeholder-text">Photo 1 — Daylight</div></div>
- <img class="photo-preview" id="previewMain1" alt="Gem1"><button class="photo-remove" aria-label="Remove photo" onclick="removePhoto(event,'photoMain1','previewMain1')">✕</button>
- <div class="photo-corner-bl"></div><div class="photo-corner-br"></div>
- <input type="file" class="photo-file-input" id="fileMain1" accept="image/*" aria-label="Upload gemstone photo 1" onchange="handleFile(this,'photoMain1','previewMain1')">
- </div>
- <div class="photo-area photo-main" id="photoMain2" onclick="triggerUpload('fileMain2')" ondragover="dragOver(event)" ondragleave="dragLeave(event)" ondrop="dropFile(event,'photoMain2','previewMain2')">
- <div class="photo-placeholder"><div class="photo-placeholder-icon">📷</div><div class="photo-placeholder-text">Photo 2 — Incand.</div></div>
- <img class="photo-preview" id="previewMain2" alt="Gem2"><button class="photo-remove" aria-label="Remove photo" onclick="removePhoto(event,'photoMain2','previewMain2')">✕</button>
- <div class="photo-corner-bl"></div><div class="photo-corner-br"></div>
- <input type="file" class="photo-file-input" id="fileMain2" accept="image/*" aria-label="Upload gemstone photo 2" onchange="handleFile(this,'photoMain2','previewMain2')">
- </div>
- </div>
- <h2 class="section-hdr">Microscopy & Magnification</h2>
- <div class="photo-grid">
- <div class="photo-area photo-micro" id="photoMicro1" onclick="triggerUpload('fileMicro1')" ondragover="dragOver(event)" ondragleave="dragLeave(event)" ondrop="dropFile(event,'photoMicro1','previewMicro1')">
- <div class="photo-placeholder"><div class="photo-placeholder-icon">🔬</div><div class="photo-placeholder-text">Microscope Img 1</div></div>
- <img class="photo-preview" id="previewMicro1" alt="M1"><button class="photo-remove" aria-label="Remove photo" onclick="removePhoto(event,'photoMicro1','previewMicro1')">✕</button>
- <div class="photo-corner-bl"></div><div class="photo-corner-br"></div>
- <input type="file" class="photo-file-input" id="fileMicro1" accept="image/*" aria-label="Upload microscope image 1" onchange="handleFile(this,'photoMicro1','previewMicro1')">
- </div>
- <div class="photo-area photo-micro" id="photoMicro2" onclick="triggerUpload('fileMicro2')" ondragover="dragOver(event)" ondragleave="dragLeave(event)" ondrop="dropFile(event,'photoMicro2','previewMicro2')">
- <div class="photo-placeholder"><div class="photo-placeholder-icon">🔬</div><div class="photo-placeholder-text">Inclusion Plotting</div><div class="photo-placeholder-sub">Click ✎ to draw</div></div>
- <img class="photo-preview" id="previewMicro2" alt="M2"><button class="photo-remove" aria-label="Remove photo" onclick="removePhoto(event,'photoMicro2','previewMicro2')">✕</button>
- <canvas class="plot-canvas" id="plotCanvas"></canvas>
- <div class="plot-controls">
- <button class="plot-btn" id="btnDraw" onclick="toggleDraw(event)" title="Draw Inclusions" aria-label="Toggle inclusion drawing mode">✎</button>
- <button class="plot-btn" onclick="clearCanvas(event)" title="Clear Drawing" aria-label="Clear drawn inclusions">🗑</button>
- </div>
- <div class="photo-corner-bl"></div><div class="photo-corner-br"></div>
- <input type="file" class="photo-file-input" id="fileMicro2" accept="image/*" aria-label="Upload microscope image 2" onchange="handleFile(this,'photoMicro2','previewMicro2')">
- </div>
- </div>
- <div class="field-row"><label class="field-label" for="inp-mag">Magnification Notes:</label><input class="field-input" id="inp-mag" placeholder="e.g. 40x, dark-field illumination"></div>
- <h2 class="section-hdr">Optical & Diagnostic Testing <span id="fluo-warning" title="">⚠️ UV Anomaly Check</span></h2>
- <div class="field-triple-row"><div class="field-row"><label class="field-label" for="inp-uvs">UV 254nm:</label><input class="field-input" id="inp-uvs" list="uv-list" placeholder="Inert / Fluo"></div><div class="field-row"><label class="field-label" for="inp-uvl">UV 365nm:</label><input class="field-input" id="inp-uvl" list="uv-list" placeholder="Inert / Fluo"></div><div class="field-row"><label class="field-label" for="inp-uv3">UV 395nm:</label><input class="field-input" id="inp-uv3" list="uv-list" placeholder="Inert / Fluo"></div></div>
- <div class="field-triple-row"><div class="field-row"><label class="field-label" for="inp-dich">Dichroscope:</label><input class="field-input" id="inp-dich" list="dich-list" placeholder="e.g. Dichroic"></div><div class="field-row"><label class="field-label" for="inp-chel">Chelsea Filter:</label><input class="field-input" id="inp-chel" list="chel-list" placeholder="e.g. Red, Green"></div><div class="field-row"><label class="field-label" for="inp-ruby">Ruby Filter:</label><input class="field-input" id="inp-ruby" list="ruby-list" placeholder="e.g. Glow"></div></div>
- <h2 class="section-hdr">Spectroscope Reading</h2>
- <div class="photo-area photo-spectro" id="photoSpectro" onclick="triggerUpload('fileSpectro')" ondragover="dragOver(event)" ondragleave="dragLeave(event)" ondrop="dropFile(event,'photoSpectro','previewSpectro')">
- <div class="photo-placeholder"><div class="photo-placeholder-icon">📊</div><div class="photo-placeholder-text">Spectroscope Graph</div></div>
- <img class="photo-preview" id="previewSpectro" alt="Spectrum"><button class="photo-remove" aria-label="Remove photo" onclick="removePhoto(event,'photoSpectro','previewSpectro')">✕</button>
- <div class="photo-corner-bl"></div><div class="photo-corner-br"></div>
- <input type="file" class="photo-file-input" id="fileSpectro" accept="image/*" aria-label="Upload spectroscope image" onchange="handleFile(this,'photoSpectro','previewSpectro')">
- </div>
- <div class="field-row"><label class="field-label" for="inp-abs">Absorption Notes:</label><input class="field-input" id="inp-abs" placeholder="e.g. 694nm Cr line, Fe bands at 450nm"></div>
- <h2 class="section-hdr">Enhancement & Treatment</h2>
- <div class="field-pair">
- <div class="field-row"><label class="field-label" for="inp-tst">Standard:</label><input class="field-input" id="inp-tst" list="treat-std-list" placeholder="e.g. None, Heat"></div>
- <div class="field-row"><label class="field-label" for="inp-tad">Additional:</label><input class="field-input" id="inp-tad" list="treat-add-list" placeholder="e.g. None, Coating"></div>
- </div>
- <div class="scale-group">
- <div class="scale-title">Enhancement Stability Index (Click to Select)</div>
- <div class="scale-boxes" data-scale="stability"><div class="scale-box" data-val="1" style="background:#FFFFFF;color:#4A4A48;">1</div><div class="scale-box" data-val="2" style="background:#E8EDF5;color:#4A4A48;">2</div><div class="scale-box" data-val="3" style="background:#D0DCEB;color:#4A4A48;">3</div><div class="scale-box" data-val="4" style="background:#B5C8DF;color:#4A4A48;">4</div><div class="scale-box" data-val="5" style="background:#95B0D2;color:#4A4A48;">5</div><div class="scale-box" data-val="6" style="background:#7094C0;color:#ffffff;">6</div><div class="scale-box" data-val="7" style="background:#4E74A8;color:#ffffff;">7</div><div class="scale-box" data-val="8" style="background:#33558C;color:#ffffff;">8</div><div class="scale-box" data-val="9" style="background:#213A68;color:#ffffff;">9</div><div class="scale-box" data-val="10" style="background:#111D35;color:#ffffff;">10</div></div>
- <div class="scale-labels"><span>Poor</span><span>Excellent</span></div>
- </div>
- </div>
- <!-- RIGHT COLUMN -->
- <div class="right-col">
- <h2 class="section-hdr alt">Grading Analysis</h2>
- <div class="field-quad-grading"><div class="field-row"><label class="field-label" for="inp-cw">Carat Wt:</label><input class="field-input" id="inp-cw" placeholder="e.g. 3.42 ct"></div><div class="field-row"><label class="field-label" for="inp-meas">Measurements:</label><input class="field-input" id="inp-meas" placeholder="e.g. 9.50 x 7.10 x 5.00 mm"></div><div class="field-row"><label class="field-label" for="inp-shape">Shape:</label><input class="field-input" id="inp-shape" list="shape-list" placeholder="e.g. Oval"></div><div class="field-row"><label class="field-label" for="inp-cut">Cut Style:</label><input class="field-input" id="inp-cut" list="cutstyle-list" placeholder="e.g. Brilliant"></div></div>
- <div class="sub-header"><div class="sub-header-text">Cut Quality Metrics</div></div>
- <div class="field-quad-row"><div class="field-row"><label class="field-label" for="inp-cq">Cut Quality:</label><input class="field-input" id="inp-cq" list="grade5-list" placeholder="V. Good"></div><div class="field-row"><label class="field-label" for="inp-sym">Symmetry:</label><input class="field-input" id="inp-sym" list="grade5-list" placeholder="Good"></div><div class="field-row"><label class="field-label" for="inp-prof">Profile:</label><input class="field-input" id="inp-prof" list="grade5-list" placeholder="Excellent"></div><div class="field-row"><label class="field-label" for="inp-pol">Polish:</label><input class="field-input" id="inp-pol" list="grade5-list" placeholder="V. Good"></div></div>
- <div class="field-quad-row"><div class="field-row"><label class="field-label" for="inp-fup">Face-up:</label><input class="field-input" id="inp-fup" list="grade5-list" placeholder="Excellent"></div><div class="field-row"><label class="field-label" for="inp-bril">Brilliance:</label><input class="field-input" id="inp-bril" list="bril-list" placeholder="85%"></div><div class="field-row"><label class="field-label" for="inp-win">Window:</label><input class="field-input" id="inp-win" placeholder="0%"></div><div class="field-row"><label class="field-label" for="inp-ext">Extinction:</label><input class="field-input" id="inp-ext" placeholder="15%"></div></div>
- <h2 class="section-hdr alt">Color Analysis</h2>
- <div class="field-quad-color"><div class="field-row"><label class="field-label" for="inp-cold">Color Code:</label><button class="inline-btn" onclick="openColorPicker()" aria-label="Open color picker">🎨</button><input class="field-input" id="inp-cold" placeholder="e.g. gB 5/5"></div><div class="field-row"><label class="field-label" for="inp-hue">Hue:</label><input class="field-input" id="inp-hue" placeholder="e.g. Greenish Blue"></div><div class="field-row"><label class="field-label" for="inp-ton">Tone:</label><input class="field-input" id="inp-ton" placeholder="e.g. Medium"></div><div class="field-row"><label class="field-label" for="inp-sat">Saturation:</label><input class="field-input" id="inp-sat" placeholder="e.g. Strong"></div></div>
- <div class="scale-group">
- <div class="scale-title">Color Grade (Click to Select)</div>
- <div class="scale-boxes" data-scale="color"><div class="scale-box" data-val="1" style="background:#FFFFFF;color:#4A4A48;">1</div><div class="scale-box" data-val="2" style="background:#E8EDF5;color:#4A4A48;">2</div><div class="scale-box" data-val="3" style="background:#D0DCEB;color:#4A4A48;">3</div><div class="scale-box" data-val="4" style="background:#B5C8DF;color:#4A4A48;">4</div><div class="scale-box" data-val="5" style="background:#95B0D2;color:#4A4A48;">5</div><div class="scale-box" data-val="6" style="background:#7094C0;color:#ffffff;">6</div><div class="scale-box" data-val="7" style="background:#4E74A8;color:#ffffff;">7</div><div class="scale-box" data-val="8" style="background:#33558C;color:#ffffff;">8</div><div class="scale-box" data-val="9" style="background:#213A68;color:#ffffff;">9</div><div class="scale-box" data-val="10" style="background:#111D35;color:#ffffff;">10</div></div>
- <div class="scale-labels"><span>Poor / Weak</span><span>Exceptional</span></div>
- </div>
- <div class="field-triple-row"><div class="field-row"><label class="field-label" for="inp-pleo">Pleochroism:</label><input class="field-input" id="inp-pleo" placeholder="e.g. Moderate"></div><div class="field-row"><label class="field-label" for="inp-zon">Color Zoning:</label><input class="field-input" id="inp-zon" placeholder="e.g. None"></div><div class="field-row"><label class="field-label" for="inp-bic">Bichroic:</label><input class="field-input" id="inp-bic" placeholder="e.g. Strong"></div></div>
- <h2 class="section-hdr alt">Clarity Assessment</h2>
- <div class="field-triple-clarity"><div class="field-row"><label class="field-label" for="inp-cgr">Clarity Grade:</label><input class="field-input" id="inp-cgr" list="clargrade-list" placeholder="e.g. Eye Clean, LI, MI"></div><div class="field-row"><label class="field-label" for="inp-ctype">Clarity Type:</label><input class="field-input" id="inp-ctype" list="clartype-list" placeholder="e.g. Type I, II, III"></div><div class="field-row"><label class="field-label" for="inp-tran">Transparency:</label><input class="field-input" id="inp-tran" list="transp-list" placeholder="e.g. Transparent"></div></div>
- <div class="scale-group">
- <div class="scale-title">Clarity Grade Scale (Click to Select)</div>
- <div class="scale-boxes" data-scale="clarity"><div class="scale-box" data-val="1" style="background:#FFFFFF;color:#4A4A48;">1</div><div class="scale-box" data-val="2" style="background:#D7E1EF;color:#4A4A48;">2</div><div class="scale-box" data-val="3" style="background:#B0C4DE;color:#4A4A48;">3</div><div class="scale-box" data-val="4" style="background:#8AAAD0;color:#ffffff;">4</div><div class="scale-box" data-val="5" style="background:#5E88B8;color:#ffffff;">5</div><div class="scale-box" data-val="6" style="background:#33558C;color:#ffffff;">6</div><div class="scale-box" data-val="7" style="background:#111D35;color:#ffffff;">7</div></div>
- <div class="scale-labels"><span>EI (Ext. Incl.)</span><span>FI (Free of Incl.)</span></div>
- </div>
- <h2 class="section-hdr alt">Quality Assessment</h2>
- <div class="scale-group">
- <div class="scale-title">Cutting Grade (Click to Select)</div>
- <div class="scale-boxes" data-scale="cutting"><div class="scale-box" data-val="1" style="background:#FFFFFF;color:#4A4A48;">1</div><div class="scale-box" data-val="2" style="background:#E8EDF5;color:#4A4A48;">2</div><div class="scale-box" data-val="3" style="background:#D0DCEB;color:#4A4A48;">3</div><div class="scale-box" data-val="4" style="background:#B5C8DF;color:#4A4A48;">4</div><div class="scale-box" data-val="5" style="background:#95B0D2;color:#4A4A48;">5</div><div class="scale-box" data-val="6" style="background:#7094C0;color:#ffffff;">6</div><div class="scale-box" data-val="7" style="background:#4E74A8;color:#ffffff;">7</div><div class="scale-box" data-val="8" style="background:#33558C;color:#ffffff;">8</div><div class="scale-box" data-val="9" style="background:#213A68;color:#ffffff;">9</div><div class="scale-box" data-val="10" style="background:#111D35;color:#ffffff;">10</div></div>
- <div class="scale-labels"><span>Poor</span><span>Excellent</span></div>
- </div>
- <div class="scale-group">
- <div class="scale-title">Finish Grade (Click to Select)</div>
- <div class="scale-boxes" data-scale="finish"><div class="scale-box" data-val="1" style="background:#FFFFFF;color:#4A4A48;">1</div><div class="scale-box" data-val="2" style="background:#E8EDF5;color:#4A4A48;">2</div><div class="scale-box" data-val="3" style="background:#D0DCEB;color:#4A4A48;">3</div><div class="scale-box" data-val="4" style="background:#B5C8DF;color:#4A4A48;">4</div><div class="scale-box" data-val="5" style="background:#95B0D2;color:#4A4A48;">5</div><div class="scale-box" data-val="6" style="background:#7094C0;color:#ffffff;">6</div><div class="scale-box" data-val="7" style="background:#4E74A8;color:#ffffff;">7</div><div class="scale-box" data-val="8" style="background:#33558C;color:#ffffff;">8</div><div class="scale-box" data-val="9" style="background:#213A68;color:#ffffff;">9</div><div class="scale-box" data-val="10" style="background:#111D35;color:#ffffff;">10</div></div>
- <div class="scale-labels"><span>Poor</span><span>Excellent</span></div>
- </div>
- <div class="field-pair"><div class="field-row"><label class="field-label" for="inp-dep">Depth %:</label><input class="field-input" id="inp-dep" placeholder="e.g. 71.9%"></div><div class="field-row"><label class="field-label" for="inp-tqi">Total Quality Index:</label><input class="field-input" id="inp-tqi" placeholder="e.g. Very Good"></div></div>
- <!-- PROPORTIONS & CLARITY CHARACTERISTICS -->
- <div style="border:1px solid var(--gold); border-radius:2px; overflow:visible; margin-top:2px; flex-grow:1; display:flex; flex-direction:column;">
- <h2 class="section-hdr" style="margin:0;">Proportions & Clarity Characteristics</h2>
- <div style="display:flex; align-items:stretch; gap:3px; padding:3px 4px; flex:1;">
- <!-- Side profile with GIA-style measurements -->
- <div style="flex:1.4; display:flex; flex-direction:column;">
- <svg id="propSideView" viewBox="0 0 400 240" width="100%" preserveAspectRatio="xMidYMid meet" style="display:block; flex:1; min-height:0;">
- <!-- Diamond outline - Crown -->
- <line x1="100" y1="42" x2="250" y2="42" stroke="var(--navy)" stroke-width="1.8"/>
- <line x1="100" y1="42" x2="40" y2="90" stroke="var(--navy)" stroke-width="1.5"/>
- <line x1="250" y1="42" x2="310" y2="90" stroke="var(--navy)" stroke-width="1.5"/>
- <!-- Girdle -->
- <line x1="40" y1="90" x2="310" y2="90" stroke="var(--navy)" stroke-width="1"/>
- <line x1="40" y1="95" x2="310" y2="95" stroke="var(--navy)" stroke-width="1"/>
- <line x1="40" y1="90" x2="40" y2="95" stroke="var(--navy)" stroke-width="0.8"/>
- <line x1="310" y1="90" x2="310" y2="95" stroke="var(--navy)" stroke-width="0.8"/>
- <!-- Pavilion -->
- <line x1="40" y1="95" x2="175" y2="215" stroke="var(--navy)" stroke-width="1.5"/>
- <line x1="310" y1="95" x2="175" y2="215" stroke="var(--navy)" stroke-width="1.5"/>
- <!-- Crown internal facets -->
- <line x1="100" y1="42" x2="65" y2="90" stroke="var(--navy)" stroke-width="0.5" opacity="0.6"/>
- <line x1="250" y1="42" x2="285" y2="90" stroke="var(--navy)" stroke-width="0.5" opacity="0.6"/>
- <line x1="125" y1="42" x2="80" y2="90" stroke="var(--navy)" stroke-width="0.4" opacity="0.5"/>
- <line x1="225" y1="42" x2="270" y2="90" stroke="var(--navy)" stroke-width="0.4" opacity="0.5"/>
- <line x1="150" y1="42" x2="100" y2="90" stroke="var(--navy)" stroke-width="0.4" opacity="0.45"/>
- <line x1="200" y1="42" x2="250" y2="90" stroke="var(--navy)" stroke-width="0.4" opacity="0.45"/>
- <line x1="175" y1="42" x2="130" y2="90" stroke="var(--navy)" stroke-width="0.3" opacity="0.35"/>
- <line x1="175" y1="42" x2="220" y2="90" stroke="var(--navy)" stroke-width="0.3" opacity="0.35"/>
- <!-- Pavilion internal facets -->
- <line x1="65" y1="95" x2="175" y2="215" stroke="var(--navy)" stroke-width="0.5" opacity="0.6"/>
- <line x1="285" y1="95" x2="175" y2="215" stroke="var(--navy)" stroke-width="0.5" opacity="0.6"/>
- <line x1="100" y1="95" x2="175" y2="195" stroke="var(--navy)" stroke-width="0.4" opacity="0.5"/>
- <line x1="250" y1="95" x2="175" y2="195" stroke="var(--navy)" stroke-width="0.4" opacity="0.5"/>
- <line x1="130" y1="95" x2="175" y2="180" stroke="var(--navy)" stroke-width="0.35" opacity="0.4"/>
- <line x1="220" y1="95" x2="175" y2="180" stroke="var(--navy)" stroke-width="0.35" opacity="0.4"/>
- <line x1="155" y1="95" x2="175" y2="170" stroke="var(--navy)" stroke-width="0.3" opacity="0.35"/>
- <line x1="195" y1="95" x2="175" y2="170" stroke="var(--navy)" stroke-width="0.3" opacity="0.35"/>
- <!-- Dimension: Table width -->
- <line x1="100" y1="30" x2="250" y2="30" stroke="var(--mid-gray)" stroke-width="0.6"/>
- <line x1="100" y1="27" x2="100" y2="42" stroke="var(--mid-gray)" stroke-width="0.4"/>
- <line x1="250" y1="27" x2="250" y2="42" stroke="var(--mid-gray)" stroke-width="0.4"/>
- <text id="propTablePct" x="175" y="27" text-anchor="middle" font-size="18" font-weight="bold" fill="var(--dark-gray)">—</text>
- <!-- Dimension: Star length (left crown) -->
- <text id="propStarPct" x="18" y="56" text-anchor="end" font-size="16" font-weight="bold" fill="var(--dark-gray)">—</text>
- <!-- Dimension: Girdle (left side) -->
- <text id="propGirdle" x="8" y="80" text-anchor="start" font-size="12" fill="var(--dark-gray)">—</text>
- <!-- Dimension: Crown angle (right) -->
- <path d="M295,90 A20,20 0 0,0 283,72" fill="none" stroke="var(--mid-gray)" stroke-width="0.5"/>
- <text id="propCrAngle" x="325" y="62" text-anchor="start" font-size="16" font-weight="bold" fill="var(--dark-gray)">—</text>
- <!-- Dimension: Crown height % (right bracket) -->
- <line x1="320" y1="42" x2="320" y2="90" stroke="var(--mid-gray)" stroke-width="0.5"/>
- <line x1="317" y1="42" x2="323" y2="42" stroke="var(--mid-gray)" stroke-width="0.5"/>
- <line x1="317" y1="90" x2="323" y2="90" stroke="var(--mid-gray)" stroke-width="0.5"/>
- <text id="propCrHeight" x="330" y="72" text-anchor="start" font-size="16" font-weight="bold" fill="var(--dark-gray)">—</text>
- <!-- Dimension: Total depth % (far right bracket) -->
- <line x1="355" y1="42" x2="355" y2="215" stroke="var(--mid-gray)" stroke-width="0.5"/>
- <line x1="352" y1="42" x2="358" y2="42" stroke="var(--mid-gray)" stroke-width="0.5"/>
- <line x1="352" y1="215" x2="358" y2="215" stroke="var(--mid-gray)" stroke-width="0.5"/>
- <text id="propDepthPct" x="365" y="133" text-anchor="start" font-size="16" font-weight="bold" fill="var(--dark-gray)">—</text>
- <!-- Dimension: Pavilion angle (right) -->
- <path d="M295,95 A25,25 0 0,1 280,118" fill="none" stroke="var(--mid-gray)" stroke-width="0.5"/>
- <text id="propPavAngle" x="310" y="135" text-anchor="start" font-size="16" font-weight="bold" fill="var(--dark-gray)">—</text>
- <!-- Dimension: Pavilion depth % (right) -->
- <text id="propPavDepth" x="330" y="165" text-anchor="start" font-size="16" font-weight="bold" fill="var(--dark-gray)">—</text>
- <!-- Dimension: Lower half % (left pavilion) -->
- <text id="propLowerHalf" x="18" y="165" text-anchor="end" font-size="16" font-weight="bold" fill="var(--dark-gray)">—</text>
- <!-- Dimension: Culet (bottom center) -->
- <text id="propCulet" x="175" y="232" text-anchor="middle" font-size="14" fill="var(--dark-gray)">—</text>
- <!-- Width label -->
- <text class="propWidth-static" x="175" y="14" text-anchor="middle" font-size="14" font-weight="bold" fill="var(--navy)">—</text>
- </svg>
- <!-- Proportion measurement inputs -->
- <div class="prop-grid">
- <div class="prop-cell"><span class="prop-cell-label">Table %</span><input class="prop-measure" id="inp-propTable" placeholder="58%"></div>
- <div class="prop-cell"><span class="prop-cell-label">Star %</span><input class="prop-measure" id="inp-propStar" placeholder="55%"></div>
- <div class="prop-cell"><span class="prop-cell-label">Crown °</span><input class="prop-measure" id="inp-propCrAngle" placeholder="35.5°"></div>
- <div class="prop-cell"><span class="prop-cell-label">Crown HT</span><input class="prop-measure" id="inp-propCrHeight" placeholder="15.0%"></div>
- <div class="prop-cell"><span class="prop-cell-label">Depth %</span><input class="prop-measure" id="inp-propDepth" placeholder="61.8%"></div>
- </div>
- <div class="prop-grid">
- <div class="prop-cell"><span class="prop-cell-label">Girdle</span><input class="prop-measure" id="inp-propGirdle" placeholder="Medium"></div>
- <div class="prop-cell"><span class="prop-cell-label">Pav °</span><input class="prop-measure" id="inp-propPavAngle" placeholder="41.0°"></div>
- <div class="prop-cell"><span class="prop-cell-label">Pav Dp</span><input class="prop-measure" id="inp-propPavDepth" placeholder="43.5%"></div>
- <div class="prop-cell"><span class="prop-cell-label">Lower ½</span><input class="prop-measure" id="inp-propLowerHalf" placeholder="80%"></div>
- <div class="prop-cell"><span class="prop-cell-label">Culet</span><input class="prop-measure" id="inp-propCulet" placeholder="None"></div>
- </div>
- <div style="font-size:7.5px; color:var(--mid-gray); text-align:center; margin-top:1px; font-weight:700;">Profile to Actual Proportions</div>
- </div>
- <!-- Face-up + Pavilion views stacked -->
- <div style="flex:0.55; display:flex; flex-direction:column; gap:2px; align-items:center; justify-content:center;">
- <!-- Face-up view with inclusion plotting -->
- <div style="text-align:center;">
- <div class="clar-svg-wrapper" id="clarWrap1" onclick="plotInclusion(event, 'clarWrap1')" style="display:inline-block;">
- <svg id="propFaceUp" viewBox="0 0 100 100" width="90" height="90">
- <circle cx="50" cy="50" r="45" fill="none" stroke="var(--navy)" stroke-width="1.2"/>
- <polygon points="50,31 63.4,36.6 69,50 63.4,63.4 50,69 36.6,63.4 31,50 36.6,36.6" fill="none" stroke="var(--navy)" stroke-width="0.7"/>
- <path d="M50,31 L62.6,19.5 M50,31 L37.4,19.5 M63.4,36.6 L62.6,19.5 M63.4,36.6 L80.5,37.4 M69,50 L80.5,37.4 M69,50 L80.5,62.6 M63.4,63.4 L80.5,62.6 M63.4,63.4 L62.6,80.5 M50,69 L62.6,80.5 M50,69 L37.4,80.5 M36.6,63.4 L37.4,80.5 M36.6,63.4 L19.5,62.6 M31,50 L19.5,62.6 M31,50 L19.5,37.4 M36.6,36.6 L19.5,37.4 M36.6,36.6 L37.4,19.5" fill="none" stroke="var(--navy)" stroke-width="0.4"/>
- <path d="M62.6,19.5 L50,5 M37.4,19.5 L50,5 M62.6,19.5 L81.8,18.2 M80.5,37.4 L81.8,18.2 M80.5,37.4 L95,50 M80.5,62.6 L95,50 M80.5,62.6 L81.8,81.8 M62.6,80.5 L81.8,81.8 M62.6,80.5 L50,95 M37.4,80.5 L50,95 M37.4,80.5 L18.2,81.8 M19.5,62.6 L18.2,81.8 M19.5,62.6 L5,50 M19.5,37.4 L5,50 M19.5,37.4 L18.2,18.2 M37.4,19.5 L18.2,18.2" fill="none" stroke="var(--navy)" stroke-width="0.4"/>
- <path d="M62.6,19.5 L67.2,8.4 M80.5,37.4 L91.6,32.8 M80.5,62.6 L91.6,67.2 M62.6,80.5 L67.2,91.6 M37.4,80.5 L32.8,91.6 M19.5,62.6 L8.4,67.2 M19.5,37.4 L8.4,32.8 M37.4,19.5 L32.8,8.4" fill="none" stroke="var(--navy)" stroke-width="0.4"/>
- </svg>
- </div>
- <div style="font-size:7px; color:var(--mid-gray); font-weight:700;">Face-up (Click to Plot)</div>
- </div>
- <!-- Pavilion / bottom-up view -->
- <div style="text-align:center;">
- <svg id="propBottomUp" viewBox="0 0 100 100" width="90" height="90">
- <circle cx="50" cy="50" r="45" fill="none" stroke="var(--navy)" stroke-width="1.2"/>
- <path d="M50,50 L50,5 M50,50 L81.8,18.2 M50,50 L95,50 M50,50 L81.8,81.8 M50,50 L50,95 M50,50 L18.2,81.8 M50,50 L5,50 M50,50 L18.2,18.2" fill="none" stroke="var(--navy)" stroke-width="0.6"/>
- <path d="M55.7,36.1 L50,5 M55.7,36.1 L81.8,18.2 M63.9,44.3 L81.8,18.2 M63.9,44.3 L95,50 M63.9,55.7 L95,50 M63.9,55.7 L81.8,81.8 M55.7,63.9 L81.8,81.8 M55.7,63.9 L50,95 M44.3,63.9 L50,95 M44.3,63.9 L18.2,81.8 M36.1,55.7 L18.2,81.8 M36.1,55.7 L5,50 M36.1,44.3 L5,50 M36.1,44.3 L18.2,18.2 M44.3,36.1 L18.2,18.2 M44.3,36.1 L50,5" fill="none" stroke="var(--navy)" stroke-width="0.4"/>
- <path d="M67.2,8.4 L55.7,36.1 M91.6,32.8 L63.9,44.3 M91.6,67.2 L63.9,55.7 M67.2,91.6 L55.7,63.9 M32.8,91.6 L44.3,63.9 M8.4,67.2 L36.1,55.7 M8.4,32.8 L36.1,44.3 M32.8,8.4 L44.3,36.1" fill="none" stroke="var(--navy)" stroke-width="0.4"/>
- </svg>
- <div style="font-size:7px; color:var(--mid-gray); font-weight:700;">Pavilion View</div>
- </div>
- </div>
- <!-- Inclusion controls & key -->
- <div style="flex:0.35; display:flex; flex-direction:column; justify-content:center; gap:3px; padding-top:4px;">
- <div class="clar-controls" style="flex-direction:column; align-items:stretch;">
- <button onclick="setInclusionType('crystal')" title="Crystal">○ Crystal</button>
- <button onclick="setInclusionType('feather')" title="Feather">➤ Feather</button>
- <button onclick="setInclusionType('needle')" title="Needle">❘ Needle</button>
- <button onclick="clearInclusions()" title="Clear All">🗑 Clear</button>
- </div>
- <div style="font-size:6.5px; color:var(--dark-gray); margin-top:3px; text-align:center;">
- <div style="font-weight:700; font-size:7px; margin-bottom:1px;">Key to Symbols</div>
- <div><span style="color:#c00;">○</span> Crystal</div>
- <div><span style="color:#c00;">◯</span> Cloud</div>
- <div><span style="color:#c00;">➤</span> Feather</div>
- <div><span style="color:#c00;">∾</span> Natural</div>
- <div><span style="color:#c00;">❘</span> Needle</div>
- <div><span style="color:#c00;">❖</span> Cavity</div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- <div class="bottom-section">
- <h2 class="section-hdr alt2">General Comments & Observations</h2>
- <textarea class="comments-area" id="inp-coms" rows="2" placeholder="Enter general comments, notable observations, testing methods used, or any additional findings..."></textarea>
- </div>
- <div class="assessment-section" id="assessmentSection">
- <div class="assessment-header"><span class="assessment-header-title">◆ Overall Gemstone Assessment</span><span class="assessment-header-auto">Auto-calculated from grading selections</span></div>
- <div class="assessment-body">
- <div class="assessment-breakdown">
- <div class="assess-item"><span class="assess-item-label">Color Grade:</span><span class="assess-item-value" id="av-color">—</span></div>
- <div class="assess-item"><span class="assess-item-label">Clarity Grade:</span><span class="assess-item-value" id="av-clarity">—</span></div>
- <div class="assess-item"><span class="assess-item-label">Cutting Grade:</span><span class="assess-item-value" id="av-cutting">—</span></div>
- <div class="assess-item"><span class="assess-item-label">Finish Grade:</span><span class="assess-item-value" id="av-finish">—</span></div>
- <div class="assess-item"><span class="assess-item-label">Stability Index:</span><span class="assess-item-value" id="av-stability">—</span></div>
- <div class="assess-item"><span class="assess-item-label">Weighted Score:</span><span class="assess-item-value" id="av-score">—</span></div>
- </div>
- <div class="assessment-grade-box grade-empty" id="gradeBox"><div class="grade-label">Overall Grade</div><div class="grade-value" id="gradeValue">—</div><div class="grade-descriptor" id="gradeDesc">Select grades above</div></div>
- </div>
- </div>
- <div class="report-footer-disclaimer">
- <div class="disclaimer-text">The findings expressed in this report represent the professional assessment of the examiner based on standard gemological testing methods and grading analysis. Most colored gemstones have been subjected to color and/or clarity enhancement treatments. This report is not a guarantee of value. All findings are opinions, not warranties or guarantees.</div>
- <div class="sig-row"><div class="sig-field"><label class="sig-label" for="inp-exam">Examiner:</label><input class="sig-input" id="inp-exam" placeholder="Name"></div><div class="sig-field"><label class="sig-label" for="inp-examdate">Date of Examination:</label><input type="date" id="inp-examdate" class="sig-input" style="font-size:10.5px;"></div></div>
- </div>
- <div class="report-footer-band"><div class="footer-diamond"></div><span class="footer-text" id="footerText"></span><div class="footer-diamond"></div></div>
- </div></div>
- <!-- ======== PAGE 2 ======== -->
- <div class="report-page" id="page2"><div class="ref-inner">
- <div class="ref-header"><div class="ref-title">GEMSTONE GRADING REFERENCE GUIDE</div><div class="header-gradient-line"></div><div class="ref-subtitle">Comprehensive Definitions, Terminology & Grading Scale Explanations</div></div>
- <div class="ref-columns">
- <div>
- <div class="ref-section ref-compact">
- <div class="ref-section-title">Treatment & Enhancement Identification</div>
- <div class="ref-entry"><span class="ref-term">None / Untreated (N)</span><span class="ref-def"> — No evidence of artificial enhancement detected through microscopy, spectroscopy, or UV fluorescence testing. Natural, untreated stones command the highest market premiums. Confirmation typically requires advanced laboratory testing.</span></div>
- <div class="ref-entry"><span class="ref-term">Heat Treatment (H)</span><span class="ref-def"> — Stone heated to 800–1800°C in controlled atmospheric conditions to permanently improve color saturation and/or dissolve silk inclusions for better clarity. The most widespread treatment in the industry; considered standard and stable for sapphire, ruby, and tanzanite. Identification clues: altered or dissolved rutile silk, discoid fractures, melted crystal inclusions.</span></div>
- <div class="ref-entry"><span class="ref-term">Irradiation (R)</span><span class="ref-def"> — Controlled exposure to gamma rays, neutron bombardment, or electron beam to modify color centers within the crystal lattice. Standard for producing London/Swiss blue topaz. Also used for some fancy-color diamonds, kunzite, and certain tourmalines. Often followed by low-temperature annealing to stabilize color.</span></div>
- <div class="ref-entry"><span class="ref-term">Diffusion (D)</span><span class="ref-def"> — Chemical elements (beryllium, titanium, iron, chromium) diffused into the stone at extreme temperatures (1600–1850°C). Surface diffusion (Ti/Fe) creates a thin color layer and is detectable by immersion. Lattice/bulk diffusion (beryllium) penetrates the entire stone and requires advanced testing.</span></div>
- <div class="ref-entry"><span class="ref-term">Filling / Fracture Filling (F)</span><span class="ref-def"> — Fractures and cavities filled with foreign material to improve apparent clarity. Emeralds are routinely oiled — classified by degree: None, Insignificant, Minor, Moderate, Significant. Ruby may receive lead-glass filling. Detection: characteristic “flash effect”, trapped gas bubbles, and flow structures within fractures.</span></div>
- <div class="ref-entry"><span class="ref-term">Coating (C) / Dyeing (Dy)</span><span class="ref-def"> — Coating: thin film applied to alter perceived color or add surface effects. Dyeing: coloring agents introduced into porous or fractured material. Common in jade, turquoise, agate. Detected by color concentrations in surface-reaching fractures or positive swab tests.</span></div>
- </div>
- <div class="ref-section ref-compact">
- <div class="ref-section-title alt">Optical & Diagnostic Testing Guide</div>
- <div class="ref-entry"><span class="ref-term">UV Fluorescence (254nm SW / 365nm LW / 395nm LED)</span><span class="ref-def"> — Record reaction intensity and observed color. Short-wave (254nm) is generally more diagnostic than long-wave. Key diagnostic patterns: natural ruby shows moderate–strong red; synthetic flame-fusion ruby shows stronger SW; natural blue sapphire is typically inert; heated sapphire may show chalky blue/green SW. Differential fluorescence helps distinguish natural from synthetic.</span></div>
- <div class="ref-entry"><span class="ref-term">Dichroscope</span><span class="ref-def"> — Reveals pleochroic colors by isolating light vibrating along different crystallographic axes. Singly refractive stones show NO dichroism. Uniaxial crystals show 2 colors. Biaxial crystals show 3 colors. Critical separations: ruby (dichroic) vs. red spinel (no dichroism); blue sapphire (dichroic) vs. blue spinel (no dichroism).</span></div>
- <div class="ref-entry"><span class="ref-term">Chelsea Filter</span><span class="ref-def"> — Transmits only deep red and yellow-green wavelengths. Chromium-bearing stones transmit red: natural emerald appears pink-red; synthetic emerald appears stronger red. Cobalt-colored glass and synthetic spinel appear strong red. Not diagnostic alone, use as a screening tool alongside other tests.</span></div>
- <div class="ref-entry"><span class="ref-term">Ruby Filter</span><span class="ref-def"> — Transmits only red wavelengths while blocking all others. Under blue or white illumination, Cr-bearing stones will glow or appear bright red/pink because chromium fluoresce red. Non-Cr red stones appear dark. Useful for confirming chromium as the color agent.</span></div>
- <div class="ref-entry"><span class="ref-term">Spectroscope</span><span class="ref-def"> — Reveals diagnostic absorption lines and bands caused by specific chromophore elements. Key markers: Chromium (Cr): sharp doublet at 694nm. Iron (Fe): 450nm band. Manganese (Mn): broad 410nm band.</span></div>
- </div>
- <div class="ref-section ref-compact">
- <div class="ref-section-title alt">Refractive Index, Optical Character & Crystal System</div>
- <div class="ref-entry"><span class="ref-term">Refractive Index (RI)</span><span class="ref-def"> — The ratio of the speed of light in air to its speed within the gemstone. Measured with a refractometer using a contact liquid (RI 1.81). The single most diagnostic quantitative measurement in gemology — each mineral species has a characteristic RI range. Doubly refractive stones produce two readings (ω/ε for uniaxial; α/β/γ for biaxial). Stones over RI 1.81 (e.g. diamond at 2.417, zircon high type ~1.93) are “over the limits” of a standard refractometer and require alternative methods (Brewster angle, reflectivity meter).</span></div>
- <div class="ref-entry"><span class="ref-term">Optical Character</span><span class="ref-def"> — Whether a gemstone is singly refractive (SR/isotropic), doubly refractive (DR/anisotropic), or aggregate (AGG/polycrystalline). DR stones are further classified as uniaxial (+/−) or biaxial (+/−) based on their optic sign, determined by observing the interference figure under a polariscope with a conoscope lens. Some isotropic stones show anomalous double refraction (ADR) due to strain — notable in garnet, spinel, and diamond.</span></div>
- <div class="ref-entry"><span class="ref-term">Birefringence</span><span class="ref-def"> — The numerical difference between a stone’s highest and lowest refractive index readings. Diagnostic for species identification: zircon has exceptionally high birefringence (0.042–0.059) causing visible facet doubling; quartz is moderate (0.009); garnet and spinel are zero (isotropic). Measured directly from refractometer readings or estimated from observed facet doubling under magnification.</span></div>
- <div class="ref-entry"><span class="ref-term">Crystal System</span><span class="ref-def"> — The fundamental symmetry framework of a mineral’s atomic structure. Seven systems: Isometric (cubic) — SR, no birefringence (diamond, garnet, spinel). Hexagonal/Trigonal — uniaxial DR (corundum, beryl, quartz, tourmaline). Tetragonal — uniaxial DR (zircon). Orthorhombic — biaxial DR (topaz, chrysoberyl, olivine). Monoclinic — biaxial DR (spodumene, orthoclase). Triclinic — biaxial DR (plagioclase, kyanite). Amorphous — SR, no crystal structure (opal, glass). Crystal system determines optical behavior, pleochroism potential, and cleavage directions.</span></div>
- </div>
- </div>
- <div>
- <div class="ref-section ref-compact">
- <div class="ref-section-title alt">Color Analysis — Comprehensive Definitions</div>
- <div class="ref-entry"><span class="ref-term">Hue</span><span class="ref-def"> — The dominant spectral color perceived by the eye. Modified hues indicate secondary color components using lowercase prefixes: “gB” = greenish Blue. The closer a stone is to a pure primary hue with minimal secondary modifiers, the more valuable it generally is.</span></div>
- <div class="ref-entry"><span class="ref-term">Tone</span><span class="ref-def"> — The relative lightness or darkness of the color, measured on a 0 (white) to 10 (black) scale. The ideal tone varies by species: rubies and sapphires are most desirable at tone 5–7; emeralds at 5–6.</span></div>
- <div class="ref-entry"><span class="ref-term">Saturation</span><span class="ref-def"> — The intensity, purity, or vividness of the hue—the most important single factor in colored stone valuation. Gray and brown are saturation modifiers that reduce a stone’s color purity.</span></div>
- <div class="ref-entry"><span class="ref-term">Color Zoning</span><span class="ref-def"> — Uneven distribution of color within a stone caused by fluctuating growth conditions during crystallization. Visible zoning under the table facet negatively impacts value significantly more than zoning visible only from the side.</span></div>
- <div class="ref-entry"><span class="ref-term">Bichroic / Color-Change Effect</span><span class="ref-def"> — A phenomenon where a stone exhibits distinctly different colors under different illumination sources (e.g., daylight vs. incandescent). Classic in alexandrite (green→red).</span></div>
- </div>
- <div class="ref-section">
- <div class="ref-section-title alt">Color Grade Scale (1–10)</div>
- <table class="ref-table">
- <tr><th>#</th><th>Rating</th><th>What This Means</th></tr>
- <tr><td class="grade-num">1–2</td><td>Poor / Weak</td><td>Very pale, strongly grayish/brownish, or severely off-hue. Low commercial desirability.</td></tr>
- <tr><td class="grade-num">3–4</td><td>Fair</td><td>Identifiable hue but significantly compromised by gray/brown modifiers or undesirable tone.</td></tr>
- <tr><td class="grade-num">5–6</td><td>Good</td><td>Pleasant, attractive color with moderate saturation. Mainstream fine jewelry quality.</td></tr>
- <tr><td class="grade-num">7–8</td><td>Very Good</td><td>Strong saturation, desirable primary hue, well-balanced medium tone. Top 10–15% of market.</td></tr>
- <tr><td class="grade-num">9–10</td><td>Exceptional</td><td>Vivid saturation, ideal primary hue, optimal tone. Museum/investment caliber. Top 1–3%.</td></tr>
- </table>
- </div>
- <div class="ref-section ref-compact">
- <div class="ref-section-title alt">Clarity Assessment — Definitions & Type System</div>
- <div class="ref-entry" style="margin-bottom:0;"><span class="ref-term">Clarity Type I</span><span class="ref-def"> — Species that grow relatively inclusion-free (aquamarine, blue topaz). Inclusions penalized heavily.</span></div>
- <div class="ref-entry" style="margin-bottom:0;"><span class="ref-term">Clarity Type II</span><span class="ref-def"> — Species that typically contain some inclusions (ruby, sapphire). Minor inclusions acceptable.</span></div>
- <div class="ref-entry" style="margin-bottom:0;"><span class="ref-term">Clarity Type III</span><span class="ref-def"> — Species that are almost always included (emerald). Assessed far more leniently.</span></div>
- </div>
- <div class="ref-section">
- <div class="ref-section-title alt">Clarity Grade Scale (1–7)</div>
- <table class="ref-table">
- <tr><th>#</th><th>Grade</th><th>What This Means</th></tr>
- <tr><td class="grade-num">1</td><td>EI₃</td><td>Prominent inclusions severely affecting appearance, durability, or both. Transparency compromised.</td></tr>
- <tr><td class="grade-num">2</td><td>EI₂</td><td>Obvious inclusions very easily seen by unaided eye. Significant negative impact on beauty.</td></tr>
- <tr><td class="grade-num">3</td><td>HI</td><td>Inclusions easily visible to the unaided eye without effort. Moderate impact on appearance.</td></tr>
- <tr><td class="grade-num">4</td><td>MI</td><td>Noticeable inclusions somewhat visible to the unaided eye. Acceptable for many applications.</td></tr>
- <tr><td class="grade-num">5</td><td>LI</td><td>Minor inclusions somewhat easy to see under 10x mag. Minimal effect on beauty.</td></tr>
- <tr><td class="grade-num">6</td><td>Eye Clean</td><td>Minor inclusions visible only under 10x mag. Appears clean to unaided eye. Fine quality.</td></tr>
- <tr><td class="grade-num">7</td><td>FI</td><td>Free of inclusions under 10x magnification. Extremely rare in most colored species.</td></tr>
- </table>
- </div>
- <div class="ref-section">
- <div class="ref-section-title alt">Cutting & Finish Grade Scale (1–10)</div>
- <table class="ref-table">
- <tr><th>#</th><th>Rating</th><th>What This Means</th></tr>
- <tr><td class="grade-num">1–2</td><td>Poor</td><td>Significant windowing (>30%), severe asymmetry, poor polish. Heavily compromised for weight.</td></tr>
- <tr><td class="grade-num">3–4</td><td>Fair</td><td>Noticeable windowing or excessive extinction. Acceptable for lower-grade commercial goods.</td></tr>
- <tr><td class="grade-num">5–6</td><td>Good</td><td>Minor imperfections. Acceptable light return (60–75%). Meets mainstream jewelry standards.</td></tr>
- <tr><td class="grade-num">7–8</td><td>Very Good</td><td>Well-proportioned, strong light return (75–85%). Fine jewelry professional grade.</td></tr>
- <tr><td class="grade-num">9–10</td><td>Excellent</td><td>Exceptional proportions, brilliant light return (85%+). Master cutter or precision machine cut.</td></tr>
- </table>
- </div>
- </div>
- </div>
- <div class="ref-columns"><div class="ref-full-width"><hr class="ref-divider">
- <div class="ref-section ref-compact">
- <div class="ref-section-title alt">Cut Quality Metrics — Field Definitions</div>
- <div class="ref-def" style="font-size:8.3px;line-height:9.2px;font-family:'Times New Roman', Times, Georgia, serif;">
- <strong>Cut Quality</strong> — Overall assessment of how well the stone has been fashioned; considers proportions, angles, and the cutter’s skill in maximizing optical performance while retaining weight. <strong>Symmetry</strong> — The precision and alignment of facets relative to each other; evaluates crown-to-pavilion alignment, facet shape uniformity, table centering, and girdle evenness. <strong>Brilliance</strong> — The percentage of white light returned to the eye from the stone’s interior and exterior surfaces; directly linked to pavilion angle and crown height; reported as a percentage: <50% (poor) to 90%+ (exceptional). <strong>Polish</strong> — The surface condition and smoothness of finished facets; examines scratch marks, burn marks, polish lines, and surface grain under magnification. <strong>Window</strong> — A transparent see-through area visible face-up, caused by light leaking through the pavilion; reported as a percentage of the face-up area: 0% = no window (ideal), >25% = significant. <strong>Face-up Appearance</strong> — The overall visual impression when viewing the stone from directly above; combines brilliance, color distribution, window, and extinction into a single aesthetic judgment. <strong>Extinction</strong> — Dark areas within the stone caused by light being absorbed rather than reflected; some extinction is normal and provides contrast, excessive extinction (>25%) reduces beauty. <strong>Profile</strong> — The side-view proportions: crown height, pavilion depth, girdle thickness, and overall depth percentage; a well-profiled stone balances weight retention with optical performance.
- </div>
- </div>
- </div></div>
- <div class="ref-columns"><div class="ref-full-width"><hr class="ref-divider">
- <div class="ref-section">
- <div class="ref-section-title">Overall Assessment Calculation — Methodology</div>
- <div class="ref-def" style="font-size:8.3px;line-height:9.2px;font-family:'Times New Roman', Times, Georgia, serif;">
- The Overall Gemstone Grade is a weighted composite calculated automatically from your grading scale selections. The weighting reflects industry consensus on value drivers for colored gemstones: <strong>Color (35%)</strong> is weighted most heavily because color is the primary determinant of desirability and value in colored stones. <strong>Clarity (20%)</strong> is normalized from the 7-point scale to a 10-point equivalent for fair comparison. <strong>Cutting Grade (20%)</strong> evaluates how well the stone has been fashioned for optical performance. <strong>Finish Grade (15%)</strong> assesses the precision and polish quality of the lapidary work. <strong>Enhancement Stability (10%)</strong> accounts for how durable and permanent any treatments are. All scales are oriented with 1 = worst and the highest number = best. The composite score maps to letter grades: <strong>A+ (9.5+) Exceptional</strong> · <strong>A (8.5–9.4) Excellent</strong> · <strong>A− (7.5–8.4) Very Good</strong> · <strong>B+ (6.5–7.4) Good</strong> · <strong>B (5.5–6.4) Above Average</strong> · <strong>B− (4.5–5.4) Average</strong> · <strong>C+ (3.5–4.4) Below Average</strong> · <strong>C (2.8–3.4) Fair</strong> · <strong>C− (2.0–2.7) Poor</strong> · <strong>D (1.5–1.9) Very Poor</strong> · <strong>F (<1.5) Failing</strong>. A minimum of 2 grading scales must be selected for the assessment to calculate. If fewer than all 5 scales are filled, the algorithm re-normalizes weights across the scales you did select, so partial data still produces a meaningful grade.
- </div>
- </div>
- </div></div>
- <div class="report-footer-band" style="margin-top:4px;"><div class="footer-diamond"></div><span class="footer-text">B G Gemological Collection · Reference Guide · See front page for specific gemstone grading results</span><div class="footer-diamond"></div></div>
- </div></div>
- </div>
- <script>
- // --- LOCAL STORAGE AUTO-SAVE LOGIC ---
- function saveToLocal(key, val) {
- try {
- localStorage.setItem('gemReport_' + key, val);
- } catch(e) {
- if(e.name === 'QuotaExceededError' || e.code === 22) {
- alert('Storage is full. Your report data may not be saved. Consider exporting to PDF and starting a new report to free up space.');
- }
- console.warn("Local storage error:", e);
- }
- }
- function loadFromLocal(key) { return localStorage.getItem('gemReport_' + key); }
- function attachAutoSave() {
- document.querySelectorAll('input, textarea').forEach(el => {
- if(el.id && el.type !== 'file') {
- const saved = loadFromLocal(el.id);
- if(saved !== null) el.value = saved;
- el.addEventListener('input', () => saveToLocal(el.id, el.value));
- }
- });
- // Load Scales
- document.querySelectorAll('.scale-boxes').forEach(container => {
- const scaleName = container.dataset.scale;
- const savedVal = loadFromLocal('scale_' + scaleName);
- if(savedVal) {
- const box = container.querySelector(`[data-val="${savedVal}"]`);
- if(box) box.classList.add('selected');
- }
- });
- // Load Images & Plot Canvas
- ['photoMain1','photoMain2','photoMicro1','photoMicro2','photoSpectro'].forEach(id => {
- const savedImg = loadFromLocal('img_' + id);
- if(savedImg) {
- const p = id.replace('photo', 'preview');
- document.getElementById(p).src = savedImg;
- document.getElementById(id).classList.add('has-image');
- }
- });
- const savedPlot = loadFromLocal('plot_canvas');
- if(savedPlot) {
- let img = new Image();
- img.onload = () => ctx.drawImage(img, 0, 0);
- img.src = savedPlot;
- }
- }
- // --- GEM DATABASE AUTO-POPULATE ---
- const groupToSpecies = {
- "Corundum": ["Corundum"],
- "Beryl": ["Beryl"],
- "Chrysoberyl": ["Chrysoberyl"],
- "Garnet": ["Pyrope", "Almandine", "Spessartine", "Grossular", "Andradite", "Uvarovite", "Pyrope-Almandine"],
- "Tourmaline": ["Elbaite", "Dravite", "Liddicoatite", "Schorl", "Uvite"],
- "Quartz": ["Quartz"],
- "Epidote": ["Zoisite"],
- "Topaz": ["Topaz"],
- "Spinel": ["Spinel"],
- "Olivine": ["Olivine"],
- "Zircon": ["Zircon"],
- "Native Element": ["Diamond"]
- };
- const speciesToVariety = {
- "Corundum": ["Ruby", "Sapphire"],
- "Beryl": ["Emerald", "Aquamarine", "Morganite"],
- "Chrysoberyl": ["Alexandrite"],
- "Pyrope": ["Rhodolite", "Garnet"],
- "Almandine": ["Rhodolite", "Garnet"],
- "Pyrope-Almandine": ["Rhodolite"],
- "Spessartine": ["Spessartite"],
- "Grossular": ["Tsavorite"],
- "Andradite": [],
- "Uvarovite": [],
- "Elbaite": ["Tourmaline", "Paraiba"],
- "Dravite": ["Tourmaline"],
- "Liddicoatite": ["Tourmaline"],
- "Schorl": ["Tourmaline"],
- "Uvite": ["Tourmaline"],
- "Quartz": ["Amethyst", "Citrine"],
- "Zoisite": ["Tanzanite"],
- "Topaz": ["Topaz"],
- "Spinel": ["Spinel"],
- "Olivine": ["Peridot"],
- "Zircon": ["Zircon"],
- "Diamond": ["Diamond"]
- };
- const gemDB = {
- "ruby": { species: ["Corundum"], group:["Corundum"] },
- "sapphire": { species: ["Corundum"], group: ["Corundum"] },
- "emerald": { species: ["Beryl"], group: ["Beryl"] },
- "aquamarine": { species: ["Beryl"], group: ["Beryl"] },
- "morganite": { species:["Beryl"], group: ["Beryl"] },
- "alexandrite": { species:["Chrysoberyl"], group: ["Chrysoberyl"] },
- "tsavorite": { species:["Grossular"], group: ["Garnet"] },
- "spessartite": { species:["Spessartine"], group: ["Garnet"] },
- "rhodolite": { species:["Pyrope-Almandine", "Pyrope", "Almandine"], group: ["Garnet"] },
- "garnet": { species:["Pyrope", "Almandine", "Spessartine", "Grossular", "Andradite", "Uvarovite"], group: ["Garnet"] },
- "tourmaline": { species:["Elbaite", "Dravite", "Liddicoatite", "Schorl", "Uvite"], group: ["Tourmaline"] },
- "paraiba": { species: ["Elbaite"], group: ["Tourmaline"] },
- "amethyst": { species: ["Quartz"], group: ["Quartz"] },
- "citrine": { species: ["Quartz"], group: ["Quartz"] },
- "tanzanite": { species: ["Zoisite"], group: ["Epidote"] },
- "topaz": { species: ["Topaz"], group: ["Topaz"] },
- "spinel": { species: ["Spinel"], group: ["Spinel"] },
- "peridot": { species: ["Olivine"], group: ["Olivine"] },
- "zircon": { species: ["Zircon"], group: ["Zircon"] },
- "diamond": { species: ["Diamond"], group: ["Native Element"] }
- };
- // Store original datalist contents at init time
- const _origSpecListHTML = document.getElementById('spec-list').innerHTML;
- const _origGrpListHTML = document.getElementById('grp-list').innerHTML;
- const _origVarListHTML = document.getElementById('variety-list').innerHTML;
- function filterDatalists(groupVal, specVal) {
- const specList = document.getElementById('spec-list');
- const varList = document.getElementById('variety-list');
- // If both fields are empty, restore original datalist contents
- if(!groupVal && !specVal) {
- if(typeof _origSpecListHTML !== 'undefined') specList.innerHTML = _origSpecListHTML;
- if(typeof _origVarListHTML !== 'undefined') varList.innerHTML = _origVarListHTML;
- return;
- }
- // All possible species and varieties
- const allSpecies = [...new Set(Object.values(groupToSpecies).flat())];
- const allVarieties = Object.keys(gemDB).map(v => v.charAt(0).toUpperCase() + v.slice(1));
- // Filter species by group
- let filteredSpecies = allSpecies;
- if(groupVal && groupToSpecies[groupVal]) {
- filteredSpecies = groupToSpecies[groupVal];
- }
- // Filter varieties by species (or by group's species if no species selected)
- let filteredVarieties = allVarieties;
- if(specVal && speciesToVariety[specVal]) {
- filteredVarieties = speciesToVariety[specVal];
- } else if(groupVal && groupToSpecies[groupVal]) {
- filteredVarieties = [];
- groupToSpecies[groupVal].forEach(sp => {
- if(speciesToVariety[sp]) filteredVarieties.push(...speciesToVariety[sp]);
- });
- }
- // Update datalists
- specList.innerHTML = '';
- filteredSpecies.forEach(s => { const o = document.createElement('option'); o.value = s; specList.appendChild(o); });
- varList.innerHTML = '';
- [...new Set(filteredVarieties)].forEach(v => { const o = document.createElement('option'); o.value = v; varList.appendChild(o); });
- }
- document.getElementById('inp-var').addEventListener('input', (e) => {
- const val = e.target.value.trim().toLowerCase();
- const specInput = document.getElementById('inp-spec');
- const grpInput = document.getElementById('inp-grp');
- const specList = document.getElementById('spec-list');
- const grpList = document.getElementById('grp-list');
- if(gemDB[val]) {
- specList.innerHTML = '';
- grpList.innerHTML = '';
- const data = gemDB[val];
- // Species
- if(data.species.length === 1) {
- specInput.value = data.species[0];
- saveToLocal('inp-spec', data.species[0]);
- } else {
- data.species.forEach(sp => {
- const opt = document.createElement('option');
- opt.value = sp;
- specList.appendChild(opt);
- });
- if(!data.species.includes(specInput.value)) specInput.value = '';
- }
- // Group
- if(data.group.length === 1) {
- grpInput.value = data.group[0];
- saveToLocal('inp-grp', data.group[0]);
- } else {
- data.group.forEach(g => {
- const opt = document.createElement('option');
- opt.value = g;
- grpList.appendChild(opt);
- });
- if(!data.group.includes(grpInput.value)) grpInput.value = '';
- }
- } else {
- // No match found — restore original full datalist options
- document.getElementById('spec-list').innerHTML = _origSpecListHTML;
- document.getElementById('grp-list').innerHTML = _origGrpListHTML;
- }
- // Auto-populate RI-related fields from species
- const specVal = document.getElementById('inp-spec').value.trim().toLowerCase();
- let riMatch = null;
- const riKeysVar = Object.keys(riRanges).sort((a, b) => b.length - a.length);
- for(const key of riKeysVar) {
- if(specVal.includes(key)) { riMatch = riRanges[key]; break; }
- }
- if(riMatch) {
- const opcharInput = document.getElementById('inp-opchar');
- const birefInput = document.getElementById('inp-biref');
- const crystalInput = document.getElementById('inp-crystal');
- if(!opcharInput.value) { opcharInput.value = riMatch.opchar; saveToLocal('inp-opchar', riMatch.opchar); }
- if(!birefInput.value) { birefInput.value = riMatch.biref; saveToLocal('inp-biref', riMatch.biref); }
- if(!crystalInput.value) { crystalInput.value = riMatch.crystal; saveToLocal('inp-crystal', riMatch.crystal); }
- }
- checkFluorescenceConflict();
- checkSGRange();
- checkRIRange();
- });
- document.getElementById('inp-spec').addEventListener('input', (e) => {
- const val = e.target.value.trim();
- saveToLocal('inp-spec', val);
- // Filter varieties by selected species
- const groupVal = document.getElementById('inp-grp').value.trim();
- filterDatalists(groupVal, val);
- // Optionally auto-fill RI-related fields from species
- const specVal = val.toLowerCase();
- let riMatch = null;
- const riKeysSpec = Object.keys(riRanges).sort((a, b) => b.length - a.length);
- for(const key of riKeysSpec) {
- if(specVal.includes(key)) { riMatch = riRanges[key]; break; }
- }
- if(riMatch) {
- const opcharInput = document.getElementById('inp-opchar');
- const birefInput = document.getElementById('inp-biref');
- const crystalInput = document.getElementById('inp-crystal');
- if(!opcharInput.value) { opcharInput.value = riMatch.opchar; saveToLocal('inp-opchar', riMatch.opchar); }
- if(!birefInput.value) { birefInput.value = riMatch.biref; saveToLocal('inp-biref', riMatch.biref); }
- if(!crystalInput.value) { crystalInput.value = riMatch.crystal; saveToLocal('inp-crystal', riMatch.crystal); }
- }
- checkSGRange();
- checkRIRange();
- });
- document.getElementById('inp-grp').addEventListener('input', (e) => {
- const val = e.target.value.trim();
- saveToLocal('inp-grp', val);
- // Filter species and varieties by selected group
- const specVal = document.getElementById('inp-spec').value.trim();
- filterDatalists(val, specVal);
- checkSGRange();
- });
- // --- FLUORESCENCE CONFLICT CHECKER ---
- function checkFluorescenceConflict() {
- const variety = document.getElementById('inp-var').value.trim().toLowerCase();
- const uvSW = document.getElementById('inp-uvs').value.trim().toLowerCase();
- const uvLW = document.getElementById('inp-uvl').value.trim().toLowerCase();
- const warnIcon = document.getElementById('fluo-warning');
- let warningMsg = "";
- if(variety.includes('sapphire') && !variety.includes('pink') && !variety.includes('yellow')) {
- if(uvSW.includes('red') || uvLW.includes('red')) {
- warningMsg = "Natural blue sapphire is typically inert. Red fluorescence may indicate synthetic origin (Verneuil).";
- }
- } else if(variety.includes('ruby')) {
- if(uvSW.includes('inert') || uvSW.includes('none') || uvLW.includes('inert') || uvLW.includes('none')) {
- warningMsg = "Natural and synthetic rubies typically fluoresce red due to Cr. Inert reaction is anomalous.";
- } else if(uvSW.includes('strong') && !uvLW.includes('strong') && uvLW.trim() !== '') {
- warningMsg = "Synthetic flame-fusion rubies often show stronger SW fluorescence than LW. Natural typically shows stronger LW.";
- }
- } else if(variety.includes('emerald')) {
- if((uvSW.includes('strong') && uvSW.includes('red')) || (uvLW.includes('strong') && uvLW.includes('red'))) {
- warningMsg = "Strong red fluorescence is very common in synthetic emeralds, though some naturals (e.g. Colombian) can also glow red.";
- }
- } else if(variety.includes('diamond')) {
- if(uvSW.includes('strong') && !uvLW.includes('strong') && uvLW.trim() !== '') {
- warningMsg = "Synthetic diamonds often show stronger SW fluorescence than LW. Natural diamonds typically show stronger LW.";
- }
- }
- if(warningMsg !== "") {
- warnIcon.style.display = 'inline-block';
- warnIcon.title = "⚠️ Warning: " + warningMsg;
- } else {
- warnIcon.style.display = 'none';
- }
- }
- document.getElementById('inp-uvs').addEventListener('input', checkFluorescenceConflict);
- document.getElementById('inp-uvl').addEventListener('input', checkFluorescenceConflict);
- // --- MEASUREMENTS AUTO-FORMATTER ---
- document.getElementById('inp-meas').addEventListener('blur', function(e) {
- let val = e.target.value.trim();
- if(!val) return;
- // Only format if input looks like measurements (2-3 numbers separated by delimiters)
- if(!/[\d.]+\s*[x×,\s]+[\d.]+/.test(val)) return;
- // Normalize any non-ASCII dashes/times signs
- val = val.replace(/[\u2013\u2014\u00d7]/g, 'x');
- let nums = val.match(/\d+(\.\d+)?/g);
- if(nums && nums.length >= 2) {
- // Limit to first 3 numeric values
- if(nums.length > 3) {
- alert('Measurements should have 2-3 dimensions (L x W or L x W x D). Extra values ignored.');
- nums = nums.slice(0, 3);
- }
- let formatted = nums.map(n => parseFloat(n).toFixed(2)).join(' x ') + ' mm';
- e.target.value = formatted;
- saveToLocal('inp-meas', formatted);
- }
- });
- // Auto-calc depth % and carat weight after measurements are formatted
- document.getElementById('inp-meas').addEventListener('blur', autoCalcDepthPct);
- document.getElementById('inp-meas').addEventListener('blur', autoCalcCaratWeight);
- document.getElementById('inp-sg').addEventListener('input', autoCalcCaratWeight);
- // --- SG ESTIMATOR ---
- // Shape-based volume correction factors for SG calculation
- const shapeCorrections = {
- 'round': 0.0020, 'oval': 0.0022, 'cushion': 0.0022, 'emerald cut': 0.0025,
- 'pear': 0.0018, 'marquise': 0.0017, 'heart': 0.0019, 'princess': 0.0024,
- 'radiant': 0.0024, 'asscher': 0.0025, 'trillion': 0.0018, 'baguette': 0.0026,
- 'cabochon': 0.0023, 'briolette': 0.0016, 'rose cut': 0.0015, 'freeform': 0.0020,
- 'hexagonal': 0.0021, 'kite': 0.0019, 'shield': 0.0020, 'trapezoid': 0.0023
- };
- function getShapeCorrection() {
- const shape = (document.getElementById('inp-shape').value || '').trim().toLowerCase();
- for(const key in shapeCorrections) {
- if(shape.includes(key)) return shapeCorrections[key];
- }
- return 0.0020; // Default to round as safest generic
- }
- function calcSG() {
- const cwStr = document.getElementById('inp-cw').value;
- const measStr = document.getElementById('inp-meas').value;
- if(!cwStr || !measStr) { alert('Please enter Carat Weight and Measurements first.'); return; }
- const cwMatch = cwStr.match(/[\d.]+/);
- const dims = measStr.match(/[\d.]+/g);
- if(!cwMatch || !dims || dims.length < 3) { alert('Ensure Weight and Measurements contain numbers (e.g. 3.42 and 9.50 x 7.10 x 5.00)'); return; }
- const wct = parseFloat(cwMatch[0]);
- const vol = parseFloat(dims[0]) * parseFloat(dims[1]) * parseFloat(dims[2]);
- const correction = getShapeCorrection();
- const sg = wct / (vol * correction);
- const finalSG = sg.toFixed(2);
- const sgInput = document.getElementById('inp-sg');
- sgInput.value = finalSG;
- saveToLocal('inp-sg', finalSG);
- if(sg < 1 || sg > 10) {
- alert('Calculated SG (' + finalSG + ') is outside expected range (1-10). Check your measurements and carat weight.');
- sgInput.classList.add('sg-out-of-range');
- } else {
- sgInput.classList.remove('sg-out-of-range');
- }
- checkSGRange();
- }
- // --- AUTO-CALCULATE CARAT WEIGHT FROM SG & MEASUREMENTS ---
- function autoCalcCaratWeight() {
- const sgStr = document.getElementById('inp-sg').value.trim();
- const measStr = document.getElementById('inp-meas').value.trim();
- const cwInput = document.getElementById('inp-cw');
- if(cwInput.value.trim()) return; // Don't overwrite manual entry
- if(!sgStr || !measStr) return;
- const sg = parseFloat(sgStr);
- const dims = measStr.match(/[\d.]+/g);
- if(isNaN(sg) || !dims || dims.length < 3) return;
- const vol = parseFloat(dims[0]) * parseFloat(dims[1]) * parseFloat(dims[2]);
- const correction = getShapeCorrection();
- const caratWeight = sg * vol * correction;
- if(caratWeight > 0 && caratWeight < 10000) {
- cwInput.value = caratWeight.toFixed(2) + ' ct';
- saveToLocal('inp-cw', cwInput.value);
- }
- }
- // --- AUTO-CALCULATE DEPTH % FROM MEASUREMENTS ---
- function autoCalcDepthPct() {
- const measStr = document.getElementById('inp-meas').value.trim();
- const depInput = document.getElementById('inp-dep');
- if(depInput.value.trim()) return; // Don't overwrite manual entry
- if(!measStr) return;
- const dims = measStr.match(/[\d.]+/g);
- if(!dims || dims.length < 3) return;
- const w = parseFloat(dims[0]);
- const h = parseFloat(dims[1]);
- const d = parseFloat(dims[2]);
- // GIA colored stone standard: depth / average(length, width)
- const avgWH = (w + h) / 2;
- if(avgWH > 0) {
- const depthPct = (d / avgWH) * 100;
- depInput.value = depthPct.toFixed(1) + '%';
- saveToLocal('inp-dep', depInput.value);
- updateProportions();
- }
- }
- // --- SG RANGE CHECKER ---
- const sgRanges = {
- "corundum": {min:3.95, max:4.10, label:"Corundum"},
- "beryl": {min:2.63, max:2.92, label:"Beryl"},
- "chrysoberyl": {min:3.68, max:3.78, label:"Chrysoberyl"},
- "pyrope": {min:3.62, max:3.87, label:"Pyrope Garnet"},
- "almandine": {min:3.95, max:4.32, label:"Almandine Garnet"},
- "spessartine": {min:4.12, max:4.20, label:"Spessartine Garnet"},
- "grossular": {min:3.40, max:3.73, label:"Grossular Garnet"},
- "andradite": {min:3.77, max:3.89, label:"Andradite Garnet"},
- "pyrope-almandine": {min:3.62, max:4.32, label:"Pyrope-Almandine Garnet"},
- "garnet": {min:3.40, max:4.32, label:"Garnet Group"},
- "quartz": {min:2.63, max:2.67, label:"Quartz"},
- "elbaite": {min:3.00, max:3.11, label:"Elbaite Tourmaline"},
- "dravite": {min:3.03, max:3.18, label:"Dravite Tourmaline"},
- "tourmaline": {min:3.00, max:3.26, label:"Tourmaline Group"},
- "zoisite": {min:3.10, max:3.38, label:"Zoisite"},
- "topaz": {min:3.49, max:3.57, label:"Topaz"},
- "spinel": {min:3.58, max:3.64, label:"Spinel"},
- "olivine": {min:3.27, max:3.48, label:"Olivine"},
- "zircon": {min:3.90, max:4.73, label:"Zircon"},
- "diamond": {min:3.50, max:3.53, label:"Diamond"}
- };
- function checkSGRange() {
- const species = document.getElementById('inp-spec').value.trim().toLowerCase();
- const sgStr = document.getElementById('inp-sg').value.trim();
- const warnIcon = document.getElementById('sg-warning');
- if(!species || !sgStr) { warnIcon.style.display = 'none'; return; }
- const sgVal = parseFloat(sgStr);
- if(isNaN(sgVal)) { warnIcon.style.display = 'none'; return; }
- // Find matching range by species name — sort keys longest-first for specificity
- let range = null;
- const sgKeys = Object.keys(sgRanges).sort((a, b) => b.length - a.length);
- for(const key of sgKeys) {
- if(species.includes(key)) { range = sgRanges[key]; break; }
- }
- if(!range) { warnIcon.style.display = 'none'; return; }
- if(sgVal < range.min - 0.05 || sgVal > range.max + 0.05) {
- warnIcon.style.display = 'inline-block';
- warnIcon.title = "\u26a0\ufe0f SG Mismatch: " + range.label + " expected range is " + range.min.toFixed(2) + " \u2013 " + range.max.toFixed(2) + ". Your value (" + sgVal.toFixed(2) + ") falls outside this range.";
- document.getElementById('inp-sg').classList.add('sg-out-of-range');
- } else {
- warnIcon.style.display = 'none';
- document.getElementById('inp-sg').classList.remove('sg-out-of-range');
- }
- }
- document.getElementById('inp-sg').addEventListener('input', checkSGRange);
- document.getElementById('inp-spec').addEventListener('input', checkSGRange);
- // --- RI RANGE CHECKER ---
- const riRanges = {
- "corundum": {min:1.757, max:1.779, biref:"0.008-0.010", opchar:"DR Uniaxial (−)", crystal:"Hexagonal (Trigonal)", label:"Corundum"},
- "beryl": {min:1.565, max:1.602, biref:"0.004-0.009", opchar:"DR Uniaxial (−)", crystal:"Hexagonal", label:"Beryl"},
- "chrysoberyl": {min:1.744, max:1.755, biref:"0.008-0.010", opchar:"DR Biaxial (+)", crystal:"Orthorhombic", label:"Chrysoberyl"},
- "pyrope": {min:1.714, max:1.756, biref:"None (SR)", opchar:"SR (Singly Refractive / Isotropic)", crystal:"Isometric (Cubic)", label:"Pyrope Garnet"},
- "almandine": {min:1.780, max:1.820, biref:"None (SR)", opchar:"SR (Singly Refractive / Isotropic)", crystal:"Isometric (Cubic)", label:"Almandine Garnet"},
- "spessartine": {min:1.795, max:1.815, biref:"None (SR)", opchar:"SR (Singly Refractive / Isotropic)", crystal:"Isometric (Cubic)", label:"Spessartine Garnet"},
- "grossular": {min:1.730, max:1.760, biref:"None (SR)", opchar:"SR (Singly Refractive / Isotropic)", crystal:"Isometric (Cubic)", label:"Grossular Garnet"},
- "andradite": {min:1.855, max:1.895, biref:"None (SR)", opchar:"SR (Singly Refractive / Isotropic)", crystal:"Isometric (Cubic)", label:"Andradite Garnet"},
- "pyrope-almandine": {min:1.714, max:1.820, biref:"None (SR)", opchar:"SR (Singly Refractive / Isotropic)", crystal:"Isometric (Cubic)", label:"Pyrope-Almandine Garnet"},
- "garnet": {min:1.714, max:1.895, biref:"None (SR)", opchar:"SR (Singly Refractive / Isotropic)", crystal:"Isometric (Cubic)", label:"Garnet Group"},
- "quartz": {min:1.544, max:1.553, biref:"0.009", opchar:"DR Uniaxial (+)", crystal:"Hexagonal (Trigonal)", label:"Quartz"},
- "elbaite": {min:1.615, max:1.655, biref:"0.014-0.021", opchar:"DR Uniaxial (−)", crystal:"Hexagonal (Trigonal)", label:"Elbaite Tourmaline"},
- "dravite": {min:1.610, max:1.650, biref:"0.014-0.020", opchar:"DR Uniaxial (−)", crystal:"Hexagonal (Trigonal)", label:"Dravite Tourmaline"},
- "tourmaline": {min:1.610, max:1.675, biref:"0.014-0.021", opchar:"DR Uniaxial (−)", crystal:"Hexagonal (Trigonal)", label:"Tourmaline Group"},
- "zoisite": {min:1.685, max:1.707, biref:"0.006-0.018", opchar:"DR Biaxial (+)", crystal:"Orthorhombic", label:"Zoisite"},
- "topaz": {min:1.609, max:1.643, biref:"0.008-0.010", opchar:"DR Biaxial (+)", crystal:"Orthorhombic", label:"Topaz"},
- "spinel": {min:1.712, max:1.736, biref:"None (SR)", opchar:"SR (Singly Refractive / Isotropic)", crystal:"Isometric (Cubic)", label:"Spinel"},
- "olivine": {min:1.635, max:1.690, biref:"0.033-0.038", opchar:"DR Biaxial (+/−)", crystal:"Orthorhombic", label:"Olivine"},
- "zircon": {min:1.810, max:1.987, biref:"0.000-0.059", opchar:"DR Uniaxial (+)", crystal:"Tetragonal", label:"Zircon"},
- "diamond": {min:2.417, max:2.420, biref:"None (SR)", opchar:"SR (Singly Refractive / Isotropic)", crystal:"Isometric (Cubic)", label:"Diamond"}
- };
- function checkRIRange() {
- const species = document.getElementById('inp-spec').value.trim().toLowerCase();
- const riStr = document.getElementById('inp-ri').value.trim();
- const warnIcon = document.getElementById('ri-warning');
- if(!species || !riStr) { warnIcon.style.display = 'none'; return; }
- // Parse RI — handle single value or range like "1.544–1.553" or "1.544-1.553" or "1.544 to 1.553"
- const riNums = riStr.match(/[\d.]+/g);
- if(!riNums || riNums.length === 0) { warnIcon.style.display = 'none'; return; }
- const riVal = parseFloat(riNums[0]);
- const riVal2 = riNums.length > 1 ? parseFloat(riNums[riNums.length - 1]) : riVal;
- if(isNaN(riVal)) { warnIcon.style.display = 'none'; return; }
- let range = null;
- const riKeys = Object.keys(riRanges).sort((a, b) => b.length - a.length);
- for(const key of riKeys) {
- if(species.includes(key)) { range = riRanges[key]; break; }
- }
- if(!range) { warnIcon.style.display = 'none'; return; }
- const tolerance = 0.010;
- if(riVal < range.min - tolerance || riVal2 > range.max + tolerance) {
- warnIcon.style.display = 'inline-block';
- warnIcon.title = "\u26a0\ufe0f RI Mismatch: " + range.label + " expected range is " + range.min.toFixed(3) + " \u2013 " + range.max.toFixed(3) + ". Your value (" + riStr + ") falls outside this range.";
- } else {
- warnIcon.style.display = 'none';
- }
- }
- document.getElementById('inp-ri').addEventListener('input', checkRIRange);
- document.getElementById('inp-spec').addEventListener('input', checkRIRange);
- // --- AUTO-POPULATE FROM SPECIES ---
- function autoPopulateFromSpecies() {
- var species = document.getElementById('inp-spec').value.trim().toLowerCase();
- if (!species) return;
- var crystalInp = document.getElementById('inp-crystal');
- var opcharInp = document.getElementById('inp-opchar');
- var birefInp = document.getElementById('inp-biref');
- var riKeys = Object.keys(riRanges).sort(function(a, b) { return b.length - a.length; });
- var match = null;
- for (var i = 0; i < riKeys.length; i++) {
- if (species.includes(riKeys[i])) { match = riRanges[riKeys[i]]; break; }
- }
- if (!match) return;
- if (!crystalInp.value.trim()) { crystalInp.value = match.crystal; saveToLocal('inp-crystal', match.crystal); }
- if (!opcharInp.value.trim()) { opcharInp.value = match.opchar; saveToLocal('inp-opchar', match.opchar); updateConditionalFields(); }
- if (!birefInp.value.trim()) { birefInp.value = match.biref; saveToLocal('inp-biref', match.biref); }
- }
- document.getElementById('inp-spec').addEventListener('change', autoPopulateFromSpecies);
- // --- CONDITIONAL FIELD VISIBILITY ---
- function updateConditionalFields() {
- var opchar = (document.getElementById('inp-opchar').value || '').trim().toLowerCase();
- var isSR = opchar.includes('sr') || opchar.includes('isotropic') || opchar.includes('singly');
- var grayFields = [
- {id:'inp-pleo', placeholder:'e.g. Moderate'},
- {id:'inp-dich', placeholder:'e.g. Dichroic'},
- {id:'inp-biref', placeholder:'0.009'}
- ];
- grayFields.forEach(function(f) {
- var inp = document.getElementById(f.id);
- if (!inp) return;
- var row = inp.closest('.field-row');
- if (isSR) {
- row.style.opacity = '0.35';
- row.style.pointerEvents = 'none';
- inp.placeholder = 'N/A (Singly Refractive)';
- } else {
- row.style.opacity = '1';
- row.style.pointerEvents = 'auto';
- inp.placeholder = f.placeholder;
- }
- });
- }
- document.getElementById('inp-opchar').addEventListener('input', updateConditionalFields);
- // --- COLOR PICKER SYSTEM ---
- const giaHues =[
- {abbr: 'R', name: 'Red', deg: 0}, {abbr: 'oR', name: 'Orangy Red', deg: 15}, {abbr: 'rO', name: 'Red-Orange', deg: 30},
- {abbr: 'O', name: 'Orange', deg: 40}, {abbr: 'yO', name: 'Yellow-Orange', deg: 50}, {abbr: 'Y', name: 'Yellow', deg: 60},
- {abbr: 'gY', name: 'Greenish Yellow', deg: 75}, {abbr: 'yG', name: 'Yellow-Green', deg: 90}, {abbr: 'G', name: 'Green', deg: 120},
- {abbr: 'bG', name: 'Bluish Green', deg: 160}, {abbr: 'gB', name: 'Greenish Blue', deg: 190}, {abbr: 'B', name: 'Blue', deg: 220},
- {abbr: 'pB', name: 'Purplish Blue', deg: 235}, {abbr: 'vB', name: 'Violetish Blue', deg: 245}, {abbr: 'V', name: 'Violet', deg: 270},
- {abbr: 'P', name: 'Purple', deg: 290}, {abbr: 'rP', name: 'Reddish Purple', deg: 315}, {abbr: 'pR', name: 'Purplish Red', deg: 340},
- {abbr: 'pK', name: 'Pink', deg: 355}, {abbr: 'Brn', name: 'Brown', deg: 25}, {abbr: 'Gy', name: 'Gray', deg: 180}
- ];
- const toneNames =["0 - Colorless","1 - Extremely Light","2 - Very Light","3 - Light","4 - Med Light","5 - Medium","6 - Med Dark","7 - Dark","8 - Very Dark","9 - Ext. Dark","10 - Black"];
- const satNames =["0 - Colorless/Near Colorless","1 - Grayish/Brownish","2 - Sl. Gray/Brown","3 - Very Sl.","4 - Mod. Strong","5 - Strong","6 - Vivid"];
- function closeCompareChart() { document.getElementById('compareModal').classList.remove('active'); }
- function openCompareChart() {
- document.getElementById('compareModal').classList.add('active');
- // Update compare modal with current data
- document.getElementById('compare-variety').textContent = document.getElementById('inp-var').value || 'Not specified';
- document.getElementById('compare-color').textContent = document.getElementById('av-color').textContent || '—';
- document.getElementById('compare-clarity').textContent = document.getElementById('av-clarity').textContent || '—';
- document.getElementById('compare-cutting').textContent = document.getElementById('av-cutting').textContent || '—';
- document.getElementById('compare-overall').textContent = document.getElementById('gradeValue').textContent || '—';
- // Pull image from whichever photo slot has an image
- const img1 = document.getElementById('previewMain1');
- const img2 = document.getElementById('previewMain2');
- const hasSrc1 = img1 && img1.src && !img1.src.endsWith('/') && img1.src !== window.location.href;
- const hasSrc2 = img2 && img2.src && !img2.src.endsWith('/') && img2.src !== window.location.href;
- const compareImg = document.getElementById('compare-img');
- if(hasSrc1 && document.getElementById('photoMain1').classList.contains('has-image')) {
- compareImg.src = img1.src;
- } else if(hasSrc2 && document.getElementById('photoMain2').classList.contains('has-image')) {
- compareImg.src = img2.src;
- } else {
- compareImg.src = '';
- }
- }
- function openColorPicker() { document.getElementById('colorModal').classList.add('active'); updateColorPreview(); }
- function closeColorPicker() { document.getElementById('colorModal').classList.remove('active'); }
- function updateColorPreview() {
- const hIdx = document.getElementById('cp-hue').value;
- const tIdx = parseInt(document.getElementById('cp-ton').value);
- const sIdx = parseInt(document.getElementById('cp-sat').value);
- const modifier = document.getElementById('cp-modifier').value;
- document.getElementById('cp-hue-label').textContent = giaHues[hIdx].abbr + " (" + giaHues[hIdx].name + ")";
- document.getElementById('cp-ton-label').textContent = toneNames[tIdx];
- document.getElementById('cp-sat-label').textContent = satNames[sIdx];
- document.getElementById('cp-mod-label').textContent = modifier ? modifier.charAt(0).toUpperCase() + modifier.slice(1) : "None";
- let hueDeg = giaHues[hIdx].deg;
- // Apply modifier hue shift — percentage-based toward modifier target hue
- const modifierTargetHues = {
- "pinkish": 350, "orangy": 30, "yellowish": 60, "greenish": 120,
- "bluish": 220, "violetish": 270, "brownish": 30, "grayish": -1
- };
- if(modifier && modifierTargetHues[modifier] !== undefined) {
- if(modifier === "grayish") {
- // Grayish reduces saturation only, no hue shift
- } else {
- const target = modifierTargetHues[modifier];
- // Calculate shortest angular distance
- let diff = target - hueDeg;
- if(diff > 180) diff -= 360;
- if(diff < -180) diff += 360;
- // Shift 30% toward the modifier hue
- hueDeg = hueDeg + diff * 0.30;
- hueDeg = ((hueDeg % 360) + 360) % 360;
- }
- }
- const lightness = Math.max(0, 100 - (tIdx * 10));
- const saturation = sIdx * 16.5;
- document.getElementById('cp-preview').style.backgroundColor = `hsl(${hueDeg}, ${saturation}%, ${lightness}%)`;
- }
- // Preset color function
- function applyPresetColor(hueIdx, toneIdx, satIdx) {
- document.getElementById('cp-hue').value = hueIdx;
- document.getElementById('cp-ton').value = toneIdx;
- document.getElementById('cp-sat').value = satIdx;
- updateColorPreview();
- }
- function applyColor() {
- const hIdx = document.getElementById('cp-hue').value;
- const tIdx = parseInt(document.getElementById('cp-ton').value);
- const sIdx = parseInt(document.getElementById('cp-sat').value);
- const modifier = document.getElementById('cp-modifier').value;
- const modifierAbbr = {
- "pinkish": "pk",
- "orangy": "o",
- "yellowish": "y",
- "greenish": "g",
- "bluish": "b",
- "violetish": "v",
- "brownish": "brn",
- "grayish": "gy"
- };
- const baseAbbr = giaHues[hIdx].abbr;
- const finalAbbr = modifier && modifierAbbr[modifier] ? modifierAbbr[modifier] + baseAbbr : baseAbbr;
- const code = finalAbbr + " " + tIdx + "/" + sIdx;
- document.getElementById('inp-cold').value = code;
- document.getElementById('inp-hue').value = giaHues[hIdx].name;
- document.getElementById('inp-ton').value = toneNames[tIdx].split(' - ')[1] || "Medium";
- document.getElementById('inp-sat').value = satNames[sIdx].split(' - ')[1] || "Strong";
- saveToLocal('inp-cold', code); saveToLocal('inp-hue', giaHues[hIdx].name);
- saveToLocal('inp-ton', document.getElementById('inp-ton').value); saveToLocal('inp-sat', document.getElementById('inp-sat').value);
- closeColorPicker();
- }
- function updateProportions() {
- var shape = (document.getElementById('inp-shape').value || '').trim().toLowerCase();
- var meas = document.getElementById('inp-meas').value || '';
- var depthPct = document.getElementById('inp-dep').value || '';
- var dims = meas.match(/[\d.]+/g);
- var w = dims && dims[0] ? parseFloat(dims[0]).toFixed(2) : '—';
- // Update width label on side profile SVG
- var propW = document.querySelector('#propSideView .propWidth-static');
- if(propW) propW.textContent = w !== '—' ? w + ' mm' : '—';
- // Auto-fill depth% input from measurements if empty
- var depthInp = document.getElementById('inp-propDepth');
- if(depthInp && !depthInp.value && depthPct) {
- depthInp.value = depthPct;
- }
- // Sync all proportion inputs to SVG text labels
- var propMap = [
- ['inp-propTable','propTablePct'], ['inp-propStar','propStarPct'],
- ['inp-propCrAngle','propCrAngle'], ['inp-propCrHeight','propCrHeight'],
- ['inp-propDepth','propDepthPct'], ['inp-propGirdle','propGirdle'],
- ['inp-propPavAngle','propPavAngle'], ['inp-propPavDepth','propPavDepth'],
- ['inp-propLowerHalf','propLowerHalf'], ['inp-propCulet','propCulet']
- ];
- propMap.forEach(function(p) {
- var inp = document.getElementById(p[0]);
- var txt = document.getElementById(p[1]);
- if(inp && txt) txt.textContent = inp.value || '—';
- });
- // Update face-up SVG based on shape
- var faceUp = document.getElementById('propFaceUp');
- if(!faceUp) return;
- var brilliantFaceUp = '<circle cx="50" cy="50" r="45" fill="none" stroke="var(--navy)" stroke-width="1.2"/>' +
- '<polygon points="50,31 63.4,36.6 69,50 63.4,63.4 50,69 36.6,63.4 31,50 36.6,36.6" fill="none" stroke="var(--navy)" stroke-width="0.7"/>' +
- '<path d="M50,31 L62.6,19.5 M50,31 L37.4,19.5 M63.4,36.6 L62.6,19.5 M63.4,36.6 L80.5,37.4 M69,50 L80.5,37.4 M69,50 L80.5,62.6 M63.4,63.4 L80.5,62.6 M63.4,63.4 L62.6,80.5 M50,69 L62.6,80.5 M50,69 L37.4,80.5 M36.6,63.4 L37.4,80.5 M36.6,63.4 L19.5,62.6 M31,50 L19.5,62.6 M31,50 L19.5,37.4 M36.6,36.6 L19.5,37.4 M36.6,36.6 L37.4,19.5" fill="none" stroke="var(--navy)" stroke-width="0.4"/>' +
- '<path d="M62.6,19.5 L50,5 M37.4,19.5 L50,5 M62.6,19.5 L81.8,18.2 M80.5,37.4 L81.8,18.2 M80.5,37.4 L95,50 M80.5,62.6 L95,50 M80.5,62.6 L81.8,81.8 M62.6,80.5 L81.8,81.8 M62.6,80.5 L50,95 M37.4,80.5 L50,95 M37.4,80.5 L18.2,81.8 M19.5,62.6 L18.2,81.8 M19.5,62.6 L5,50 M19.5,37.4 L5,50 M19.5,37.4 L18.2,18.2 M37.4,19.5 L18.2,18.2" fill="none" stroke="var(--navy)" stroke-width="0.4"/>' +
- '<path d="M62.6,19.5 L67.2,8.4 M80.5,37.4 L91.6,32.8 M80.5,62.6 L91.6,67.2 M62.6,80.5 L67.2,91.6 M37.4,80.5 L32.8,91.6 M19.5,62.6 L8.4,67.2 M19.5,37.4 L8.4,32.8 M37.4,19.5 L32.8,8.4" fill="none" stroke="var(--navy)" stroke-width="0.4"/>';
- var crosshairs = '<line x1="50" y1="2" x2="50" y2="98" stroke="var(--light-gray)" stroke-width="0.5"/><line x1="10" y1="50" x2="90" y2="50" stroke="var(--light-gray)" stroke-width="0.5"/><line x1="20" y1="15" x2="80" y2="85" stroke="var(--light-gray)" stroke-width="0.5"/><line x1="80" y1="15" x2="20" y2="85" stroke="var(--light-gray)" stroke-width="0.5"/>';
- if(shape.includes('round') || !shape) {
- faceUp.innerHTML = brilliantFaceUp;
- } else if(shape.includes('princess') || shape.includes('square')) {
- faceUp.innerHTML = '<rect x="8" y="8" width="84" height="84" fill="none" stroke="var(--navy)" stroke-width="1.5"/>' + crosshairs;
- } else if(shape.includes('emerald') || shape.includes('baguette')) {
- faceUp.innerHTML = '<polygon points="15,5 85,5 92,15 92,85 85,95 15,95 8,85 8,15" fill="none" stroke="var(--navy)" stroke-width="1.5"/>' + crosshairs;
- } else if(shape.includes('pear')) {
- faceUp.innerHTML = '<path d="M50,5 C75,5 95,35 95,60 C95,82 75,95 50,95 C25,95 5,82 5,60 C5,35 25,5 50,5Z" fill="none" stroke="var(--navy)" stroke-width="1.5"/>' + crosshairs;
- } else if(shape.includes('marquise')) {
- faceUp.innerHTML = '<ellipse cx="50" cy="50" rx="25" ry="48" fill="none" stroke="var(--navy)" stroke-width="1.5"/>' + crosshairs;
- } else if(shape.includes('heart')) {
- faceUp.innerHTML = '<path d="M50,90 L10,45 C5,25 20,10 35,10 C42,10 48,15 50,22 C52,15 58,10 65,10 C80,10 95,25 90,45Z" fill="none" stroke="var(--navy)" stroke-width="1.5"/>' + crosshairs;
- } else if(shape.includes('cushion')) {
- faceUp.innerHTML = '<rect x="8" y="8" width="84" height="84" rx="18" ry="18" fill="none" stroke="var(--navy)" stroke-width="1.5"/>' + crosshairs;
- } else if(shape.includes('trillion') || shape.includes('triangle')) {
- faceUp.innerHTML = '<polygon points="50,5 95,90 5,90" fill="none" stroke="var(--navy)" stroke-width="1.5"/>' + crosshairs;
- } else {
- faceUp.innerHTML = brilliantFaceUp;
- }
- }
- // --- COMPARISON CHART ---
- // --- BASIC LOGIC & UTILS ---
- function generateReportNum(){var n=new Date(),y=String(n.getFullYear()).slice(2),m=String(n.getMonth()+1).padStart(2,'0'),s=String(Math.floor(Math.random()*9999999)).padStart(7,'0');return'BG-'+y+m+'-'+s}
- function setReportNum(n){document.getElementById('reportNum').textContent=n; updateFooter(n); saveToLocal('reportNum', n);}
- function regenerateNum(){setReportNum(generateReportNum());}
- function updateFooter(n){var d=new Date(),mo=['January','February','March','April','May','June','July','August','September','October','November','December'];document.getElementById('footerText').textContent='Report '+n+' \u00b7 B G Gemological Collection \u00b7 '+mo[d.getMonth()]+' '+d.getDate()+', '+d.getFullYear()}
- function triggerUpload(id){if(!document.getElementById(id).parentElement.classList.contains('has-image'))document.getElementById(id).click()}
- function handleFile(i,a,p){if(i.files[0])loadImage(i.files[0],a,p)}
- function loadImage(f,a,p){
- var r=new FileReader();
- r.onload=function(e){
- var img = new Image();
- img.onload = function() {
- var canvas = document.createElement('canvas');
- var ctx2 = canvas.getContext('2d');
- var maxW = 600;
- var scale = Math.min(1, maxW / img.width);
- canvas.width = img.width * scale; canvas.height = img.height * scale;
- ctx2.drawImage(img, 0, 0, canvas.width, canvas.height);
- var dataURL = canvas.toDataURL('image/jpeg', 0.85);
- document.getElementById(p).src = dataURL;
- document.getElementById(a).classList.add('has-image');
- saveToLocal('img_' + a, dataURL);
- }
- img.src = e.target.result;
- };
- r.readAsDataURL(f);
- }
- function removePhoto(e,a,p){
- e.stopPropagation();
- document.getElementById(p).src='';
- document.getElementById(a).classList.remove('has-image');
- var f=document.getElementById(a).querySelector('input[type="file"]');
- if(f)f.value='';
- localStorage.removeItem('gemReport_img_' + a);
- if(a === 'photoMicro2') clearCanvas(e);
- }
- function dragOver(e){e.preventDefault();e.currentTarget.classList.add('drag-over')}
- function dragLeave(e){e.currentTarget.classList.remove('drag-over')}
- function dropFile(e,a,p){e.preventDefault();e.currentTarget.classList.remove('drag-over');var f=e.dataTransfer.files[0];if(f&&f.type.startsWith('image/'))loadImage(f,a,p)}
- document.addEventListener('click',function(e){
- if(!e.target.classList.contains('scale-box'))return;
- var p=e.target.closest('.scale-boxes'),w=e.target.classList.contains('selected');
- p.querySelectorAll('.scale-box').forEach(function(b){b.classList.remove('selected')});
- if(!w) {
- e.target.classList.add('selected');
- saveToLocal('scale_' + p.dataset.scale, e.target.dataset.val);
- } else {
- localStorage.removeItem('gemReport_scale_' + p.dataset.scale);
- }
- recalculateGrade();
- });
- // --- CLARITY AUTO-CALCULATION ---
- // Maps clarity grade + type + transparency to a 1-7 scale value
- function autoClarityScale() {
- const grade = document.getElementById('inp-cgr').value.trim().toLowerCase();
- const type = document.getElementById('inp-ctype').value.trim().toLowerCase();
- const transp = document.getElementById('inp-tran').value.trim().toLowerCase();
- if(!grade) return; // Need at least a clarity grade
- // Base score from clarity grade descriptor — lookup table for unambiguous matching
- const clarityBaseScores = {
- 'fi': 7, 'flawless': 7, 'internally flawless': 7,
- 'eye clean': 6,
- 'li': 5, 'lightly included': 5,
- 'mi': 4, 'moderately included': 4,
- 'hi': 3, 'heavily included': 3,
- 'ei\u2083': 1, 'severely included': 1,
- 'ei\u2082': 2, 'excessively included': 2,
- 'ei': 2
- };
- let base = null;
- // Try full string match first
- if (clarityBaseScores.hasOwnProperty(grade)) {
- base = clarityBaseScores[grade];
- } else {
- // Extract abbreviation prefix (letters + subscript digits)
- const abbrMatch = grade.match(/^([a-z\u2080-\u2089]+)/);
- const abbr = abbrMatch ? abbrMatch[1] : '';
- if (abbr && clarityBaseScores.hasOwnProperty(abbr)) {
- base = clarityBaseScores[abbr];
- } else {
- // Try matching descriptive text inside parentheses
- const descMatch = grade.match(/\(([^)]+)\)/);
- const desc = descMatch ? descMatch[1].toLowerCase() : '';
- if (desc && clarityBaseScores.hasOwnProperty(desc)) {
- base = clarityBaseScores[desc];
- }
- }
- }
- if (base === null) return; // Unrecognized grade
- // Type modifier: Type III stones are graded more leniently (bump up), Type I more strictly (bump down)
- if(type.startsWith('type iii') || type.startsWith('type 3') || type.includes('almost always')) {
- if(base < 7) base = Math.min(7, base + 1); // lenient: +1
- } else if(type.startsWith('type i') && !type.startsWith('type ii') && !type.startsWith('type iii')) {
- if(base > 1) base = Math.max(1, base - 1); // strict: -1
- }
- // Type II: no modifier (standard)
- // Transparency penalty: anything below transparent reduces score
- if(transp.includes('opaque')) { base = Math.max(1, base - 2); }
- else if(transp.includes('semi-translucent')) { base = Math.max(1, base - 2); }
- else if(transp.includes('translucent') && !transp.includes('semi')) { base = Math.max(1, base - 1); }
- else if(transp.includes('semi-transparent')) { base = Math.max(1, base - 1); }
- // Transparent: no penalty
- base = Math.max(1, Math.min(7, base));
- // Set the clarity scale selection
- const container = document.querySelector('.scale-boxes[data-scale="clarity"]');
- container.querySelectorAll('.scale-box').forEach(b => b.classList.remove('selected'));
- const target = container.querySelector('[data-val="' + base + '"]');
- if(target) {
- target.classList.add('selected');
- saveToLocal('scale_clarity', base);
- recalculateGrade();
- }
- }
- document.getElementById('inp-cgr').addEventListener('input', autoClarityScale);
- document.getElementById('inp-ctype').addEventListener('input', autoClarityScale);
- document.getElementById('inp-tran').addEventListener('input', autoClarityScale);
- // --- PLOTTING CANVAS LOGIC ---
- (function() {
- const plotCanvas = document.getElementById('plotCanvas');
- const ctx = plotCanvas.getContext('2d');
- let isDrawing = false, drawMode = false;
- window.initCanvas = function() {
- plotCanvas.width = plotCanvas.parentElement.clientWidth;
- plotCanvas.height = plotCanvas.parentElement.clientHeight;
- };
- window.toggleDraw = function(e) {
- e.stopPropagation();
- drawMode = !drawMode;
- plotCanvas.style.pointerEvents = drawMode ? 'auto' : 'none';
- document.getElementById('btnDraw').classList.toggle('active', drawMode);
- };
- window.clearCanvas = function(e) {
- e.stopPropagation();
- ctx.clearRect(0, 0, plotCanvas.width, plotCanvas.height);
- localStorage.removeItem('gemReport_plot_canvas');
- };
- plotCanvas.addEventListener('mousedown', function(e) {
- isDrawing = true;
- ctx.beginPath();
- ctx.moveTo(e.offsetX, e.offsetY);
- });
- plotCanvas.addEventListener('mousemove', function(e) {
- if (isDrawing) {
- ctx.strokeStyle = '#e60000';
- ctx.lineWidth = 1.5;
- ctx.lineCap = 'round';
- ctx.lineTo(e.offsetX, e.offsetY);
- ctx.stroke();
- }
- });
- plotCanvas.addEventListener('mouseup', function() {
- isDrawing = false;
- saveToLocal('plot_canvas', plotCanvas.toDataURL());
- });
- plotCanvas.addEventListener('mouseleave', function() { isDrawing = false; });
- })();
- // --- CLARITY DIAGRAM INCLUSION PLOTTING ---
- (function() {
- let currentInclusionType = 'crystal';
- const inclusionSymbols = {
- 'crystal': '\u25CB', 'cloud': '\u25EF', 'feather': '\u279C',
- 'natural': '\u224E', 'needle': '\u2758', 'cavity': '\u2756'
- };
- var inclusionHistory = [];
- window.setInclusionType = function(type) { currentInclusionType = type; };
- window.plotInclusion = function(event, wrapperId) {
- const wrapper = document.getElementById(wrapperId);
- const rect = wrapper.getBoundingClientRect();
- const x = event.clientX - rect.left;
- const y = event.clientY - rect.top;
- const mark = document.createElement('div');
- mark.className = 'inclusion-mark';
- mark.style.left = x + 'px';
- mark.style.top = y + 'px';
- mark.title = currentInclusionType;
- if (currentInclusionType === 'feather') {
- mark.style.borderRadius = '0';
- mark.style.width = '8px';
- mark.style.height = '3px';
- mark.style.transform = 'translate(-50%,-50%) rotate(-30deg)';
- } else if (currentInclusionType === 'needle') {
- mark.style.borderRadius = '0';
- mark.style.width = '2px';
- mark.style.height = '8px';
- }
- wrapper.appendChild(mark);
- inclusionHistory.push(mark);
- window.saveInclusionData();
- };
- window.undoInclusion = function() {
- if (inclusionHistory.length === 0) return;
- var lastMark = inclusionHistory.pop();
- lastMark.remove();
- window.saveInclusionData();
- };
- window.clearInclusions = function() {
- document.querySelectorAll('.inclusion-mark').forEach(function(m) { m.remove(); });
- inclusionHistory = [];
- localStorage.removeItem('gemReport_inclusions');
- };
- document.addEventListener('keydown', function(e) {
- if ((e.ctrlKey || e.metaKey) && e.key === 'z') {
- var tag = document.activeElement ? document.activeElement.tagName : '';
- if (tag !== 'INPUT' && tag !== 'TEXTAREA') {
- e.preventDefault();
- window.undoInclusion();
- }
- }
- });
- window.saveInclusionData = function() {
- const data = [];
- document.querySelectorAll('.clar-svg-wrapper').forEach(function(wrapper) {
- wrapper.querySelectorAll('.inclusion-mark').forEach(function(mark) {
- data.push({
- wrapperId: wrapper.id,
- left: mark.style.left,
- top: mark.style.top,
- type: mark.title,
- width: mark.style.width,
- height: mark.style.height,
- borderRadius: mark.style.borderRadius,
- transform: mark.style.transform
- });
- });
- });
- saveToLocal('inclusions', JSON.stringify(data));
- };
- window.loadInclusionData = function() {
- const saved = loadFromLocal('inclusions');
- if (!saved) return;
- try {
- const data = JSON.parse(saved);
- data.forEach(function(d) {
- const wrapper = document.getElementById(d.wrapperId);
- if (!wrapper) return;
- const mark = document.createElement('div');
- mark.className = 'inclusion-mark';
- mark.style.left = d.left;
- mark.style.top = d.top;
- mark.title = d.type;
- if (d.width) mark.style.width = d.width;
- if (d.height) mark.style.height = d.height;
- if (d.borderRadius) mark.style.borderRadius = d.borderRadius;
- if (d.transform) mark.style.transform = d.transform;
- wrapper.appendChild(mark);
- });
- } catch(e) { console.warn('Failed to load inclusion data:', e); }
- };
- })();
- // --- GRADING ALGORITHM & COLOR CODING ---
- function getScaleValue(name){var c=document.querySelector('[data-scale="'+name+'"]');if(!c)return null;var s=c.querySelector('.scale-box.selected');return s?parseInt(s.dataset.val):null}
- function getScaleMax(name){var c=document.querySelector('[data-scale="'+name+'"]');return c?c.querySelectorAll('.scale-box').length:10}
- function valToLabel(v,m){var n=v/m*10;if(n>=9.5)return'Exceptional';if(n>=8)return'Excellent';if(n>=6.5)return'Very Good';if(n>=5)return'Good';if(n>=3.5)return'Fair';return'Poor'}
- function compositeToGrade(s){
- if(s>=9.5)return{letter:'A+',desc:'Exceptional', bg:'#B8963E'};
- if(s>=8.5)return{letter:'A',desc:'Excellent', bg:'#7d8c97'};
- if(s>=7.5)return{letter:'A\u2212',desc:'Very Good', bg:'#4A90A4'};
- if(s>=6.5)return{letter:'B+',desc:'Good', bg:'#3A6A8A'};
- if(s>=5.5)return{letter:'B',desc:'Above Avg.', bg:'#3A5585'};
- if(s>=4.5)return{letter:'B\u2212',desc:'Average', bg:'#4A4A7A'};
- if(s>=3.5)return{letter:'C+',desc:'Below Avg.', bg:'#C4722A'};
- if(s>=2.8)return{letter:'C',desc:'Fair', bg:'#C4722A'};
- if(s>=2.0)return{letter:'C\u2212',desc:'Poor', bg:'#A85A20'};
- if(s>=1.5)return{letter:'D',desc:'Very Poor', bg:'#8B2E2E'};
- return{letter:'F',desc:'Failing', bg:'#CC0000'};
- }
- function recalculateGrade(){
- var scales={color:{val:getScaleValue('color'),max:getScaleMax('color'),weight:0.35,el:'av-color'},clarity:{val:getScaleValue('clarity'),max:getScaleMax('clarity'),weight:0.20,el:'av-clarity'},cutting:{val:getScaleValue('cutting'),max:getScaleMax('cutting'),weight:0.20,el:'av-cutting'},finish:{val:getScaleValue('finish'),max:getScaleMax('finish'),weight:0.15,el:'av-finish'},stability:{val:getScaleValue('stability'),max:getScaleMax('stability'),weight:0.10,el:'av-stability'}};
- var tw=0,ws=0,fc=0;
- for(var k in scales){var s=scales[k],el=document.getElementById(s.el);if(s.val!==null){var n=(s.val/s.max)*10,l=valToLabel(s.val,s.max);el.textContent=s.val+'/'+s.max+' \u2014 '+l;el.classList.remove('empty');ws+=n*s.weight;tw+=s.weight;fc++}else{el.textContent='\u2014';el.classList.add('empty')}}
- var gb=document.getElementById('gradeBox'),gv=document.getElementById('gradeValue'),gd=document.getElementById('gradeDesc'),se=document.getElementById('av-score');
- if(fc>=2&&tw>0){
- var c=ws/tw,g=compositeToGrade(c);
- gv.textContent=g.letter;
- gd.textContent=g.desc;
- gb.style.backgroundColor = g.bg;
- gb.classList.remove('grade-empty');
- se.textContent=c.toFixed(1)+' / 10';
- se.classList.remove('empty');
- } else {
- gv.textContent='\u2014';
- gd.textContent=fc===0?'Select grades above':'Need 2+ scales';
- gb.style.backgroundColor = 'var(--navy)';
- gb.classList.add('grade-empty');
- se.textContent='\u2014';
- se.classList.add('empty');
- }
- updateGradeSummary();
- }
- // --- GRADE SUMMARY → GENERAL COMMENTS ---
- function updateGradeSummary() {
- var coms = document.getElementById('inp-coms');
- var marker = '\u2014 Grade Summary \u2014';
- var existingText = coms.value;
- var markerIdx = existingText.indexOf(marker);
- var userText = markerIdx >= 0 ? existingText.substring(0, markerIdx).trimEnd() : existingText.trimEnd();
- var scaleNames = {color:'Color',clarity:'Clarity',cutting:'Cutting',finish:'Finish',stability:'Stability'};
- var parts = [];
- for (var k in scaleNames) {
- var v = getScaleValue(k), m = getScaleMax(k);
- if (v !== null) parts.push(scaleNames[k] + ': ' + valToLabel(v, m) + ' (' + v + '/' + m + ')');
- }
- if (parts.length === 0) {
- if (markerIdx >= 0) { coms.value = userText; saveToLocal('inp-coms', coms.value); }
- return;
- }
- var summary = marker + '\n' + parts.join('. ') + '.';
- var gv = document.getElementById('gradeValue').textContent;
- var gd = document.getElementById('gradeDesc').textContent;
- if (gv && gv !== '\u2014') summary += ' Overall: ' + gv + ' (' + gd + ').';
- coms.value = (userText ? userText + '\n\n' : '') + summary;
- saveToLocal('inp-coms', coms.value);
- }
- // --- NEW REPORT & PDF EXPORT ---
- function newReport(){
- if(!confirm('Clear all fields, photos, and start a new report?')) return;
- document.querySelectorAll('.field-input,.meta-input,.sig-input,.prop-measure').forEach(i=>i.value='');
- document.querySelectorAll('.comments-area').forEach(t=>t.value='');
- document.querySelectorAll('input[type="date"]').forEach(d=>d.value='');
- document.querySelectorAll('.scale-box.selected').forEach(b=>b.classList.remove('selected'));['photoMain1','photoMain2','photoMicro1','photoMicro2','photoSpectro'].forEach(id=>{
- var a=document.getElementById(id); if(!a)return;
- a.classList.remove('has-image');
- var p=a.querySelector('.photo-preview'); if(p)p.src='';
- var f=a.querySelector('input[type="file"]'); if(f)f.value='';
- });
- clearCanvas({stopPropagation:function(){}});
- document.getElementById('fluo-warning').style.display = 'none';
- Object.keys(localStorage).forEach(key => { if(key.startsWith('gemReport_')) localStorage.removeItem(key); });
- setReportNum(generateReportNum());
- var d = new Date().toISOString().split('T')[0];
- document.getElementById('inp-date').value = d;
- saveToLocal('inp-date', d);
- recalculateGrade();
- updateProportions();
- clearInclusions();
- window.scrollTo(0,0);
- }
- function exportPDF() {
- // Save scroll position and scroll to top to fix html2canvas rendering
- const savedScrollX = window.scrollX;
- const savedScrollY = window.scrollY;
- window.scrollTo(0, 0);
- document.body.classList.add('exporting');
- // Replace blank fields with italic "N/A" for PDF
- const blankFields = [];
- document.querySelectorAll('.field-input, .meta-input, .comments-area, .sig-input').forEach(el => {
- if(!el.value || el.value.trim() === '') {
- const na = document.createElement('span');
- na.className = 'pdf-na';
- na.textContent = 'N/A';
- el.style.display = 'none';
- el.parentNode.insertBefore(na, el.nextSibling);
- blankFields.push({ el, na });
- }
- });
- const element = document.getElementById('pdfExportWrapper');
- const opt = {
- margin: 0,
- filename: document.getElementById('reportNum').textContent + '_Gem_Report.pdf',
- image: { type: 'jpeg', quality: 0.98 },
- html2canvas: { scale: 2, useCORS: true, logging: false, scrollX: 0, scrollY: 0, windowWidth: document.documentElement.scrollWidth, windowHeight: document.documentElement.scrollHeight },
- jsPDF: { unit: 'in', format: 'letter', orientation: 'portrait' },
- pagebreak: { mode: 'css', avoid: '.report-page' }
- };
- if(typeof html2pdf === 'undefined') {
- alert('PDF library failed to load. Please check your internet connection and refresh the page.');
- document.body.classList.remove('exporting');
- blankFields.forEach(({ el, na }) => { el.style.display = ''; na.remove(); });
- window.scrollTo(savedScrollX, savedScrollY);
- return;
- }
- html2pdf().set(opt).from(element).save().then(() => {
- document.body.classList.remove('exporting');
- blankFields.forEach(({ el, na }) => {
- el.style.display = '';
- na.remove();
- });
- window.scrollTo(savedScrollX, savedScrollY);
- }).catch(err => {
- document.body.classList.remove('exporting');
- blankFields.forEach(({ el, na }) => { el.style.display = ''; na.remove(); });
- window.scrollTo(savedScrollX, savedScrollY);
- alert('PDF export failed: ' + err.message);
- console.error('PDF export error:', err);
- });
- }
- // --- MODALS & INIT ---
- function showHelp(){document.getElementById('helpModal').classList.add('active')}
- function closeHelp(){document.getElementById('helpModal').classList.remove('active')}
- // Click outside modal to close (for help, compare, and color picker modals)
- document.addEventListener('click', function(e) {
- if(e.target.classList.contains('modal-overlay')) {
- e.target.classList.remove('active');
- }
- });
- window.addEventListener('DOMContentLoaded',function(){
- if(!loadFromLocal('reportNum')) {
- setReportNum(generateReportNum());
- document.getElementById('inp-date').value=new Date().toISOString().split('T')[0];
- } else {
- setReportNum(loadFromLocal('reportNum'));
- }
- attachAutoSave();
- setTimeout(() => { checkFluorescenceConflict(); checkSGRange(); checkRIRange(); updateConditionalFields(); }, 200); // Check anomalies on load
- recalculateGrade();
- updateProportions();
- loadInclusionData();
- // Event listeners for proportions update
- document.getElementById('inp-shape').addEventListener('input', updateProportions);
- document.getElementById('inp-meas').addEventListener('input', updateProportions);
- document.getElementById('inp-dep').addEventListener('input', updateProportions);
- // Sync proportion measurement inputs to SVG labels
- document.querySelectorAll('.prop-measure').forEach(function(inp) {
- inp.addEventListener('input', updateProportions);
- });
- // Accessibility: tabindex and keyboard navigation on scale boxes
- document.querySelectorAll('.scale-box').forEach(function(box) {
- box.setAttribute('tabindex', '0');
- box.setAttribute('role', 'button');
- box.setAttribute('aria-label', 'Grade ' + box.dataset.val);
- box.addEventListener('keydown', function(e) {
- if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); box.click(); }
- });
- });
- // Accessibility: tabindex on color presets
- document.querySelectorAll('.cp-preset').forEach(function(btn) {
- btn.setAttribute('tabindex', '0');
- btn.setAttribute('role', 'button');
- });
- });
- window.addEventListener('load', initCanvas);
- window.addEventListener('resize', initCanvas);
- </script>
- </body>
- </html>
Advertisement