| def render_sequence(sequence, pam_sites): | |
| html = """ | |
| <div style=' | |
| font-family: monospace; | |
| font-size: 14px; | |
| line-height: 1.8; | |
| word-wrap: break-word; | |
| background-color: #111827; | |
| color: #e5e7eb; | |
| padding: 20px; | |
| border-radius: 12px; | |
| overflow-x: auto; | |
| '> | |
| """ | |
| guide_positions = {} | |
| pam_positions = {} | |
| cut_positions = set() | |
| for idx, site in enumerate(pam_sites): | |
| start = site["position"] | |
| guide_len = len(site["guide"]) | |
| for i in range(start, start + guide_len): | |
| guide_positions[i] = idx | |
| pam_start = start + guide_len | |
| for i in range(pam_start, pam_start + 3): | |
| pam_positions[i] = idx | |
| cut_positions.add(site["cut_site"]) | |
| for i, base in enumerate(sequence): | |
| style = "" | |
| if i in guide_positions: | |
| style += "background-color:#2563eb;color:white;" | |
| if i in pam_positions: | |
| style += "background-color:#dc2626;color:white;font-weight:bold;" | |
| if i in cut_positions: | |
| style += "border-bottom:3px solid #22c55e;" | |
| if i % 10 == 0: | |
| html += f"<span style='color:#9ca3af;'> {i} </span>" | |
| html += f"<span style='{style}'>{base}</span>" | |
| if (i + 1) % 80 == 0: | |
| html += "<br><br>" | |
| html += """ | |
| <br><br> | |
| <div style='margin-top:20px;'> | |
| <div> | |
| <span style='background:#2563eb;color:white;padding:4px;border-radius:4px;'> | |
| Guide RNA | |
| </span> | |
| </div> | |
| <br> | |
| <div> | |
| <span style='background:#dc2626;color:white;padding:4px;border-radius:4px;'> | |
| PAM Site (NGG) | |
| </span> | |
| </div> | |
| <br> | |
| <div> | |
| <span style='border-bottom:3px solid #22c55e;padding:2px;'> | |
| Cas9 Cut Site | |
| </span> | |
| </div> | |
| </div> | |
| </div> | |
| """ | |
| return html |