Files
provenance/frontend/app/trees/[id]/tree/chart.css
T
justin 690a6da659 Tree: a Legend by the pan/zoom hint, and clickable ×N duplicate badges
Two small tree-view aids prompted by "why do some people show ×2".

- Legend: a hover/focus "Legend" link next to the "drag to pan…" hint, explaining
  the ×N badge (a person drawn N times in the view because they connect through
  more than one line — a shared ancestor or an intermarriage), the gender card
  colors, and the pan/zoom/recenter controls.
- The ×N badge is now clearly clickable (cursor + hover state); clicking it
  flashes every copy of that person in the current view (a bronze outline pulse),
  so you can spot where else they appear. Implemented by delegating on the chart
  container and matching the d3-bound person id across cards; capture-phase +
  stopPropagation so a badge click flashes instead of recentering.

Frontend only. Honest follow-up: flashing finds copies that are on-screen; a true
"fly to an off-screen copy" needs d3-zoom transform work (the chart pans by
transform, not scroll) — a later enhancement.

Signed-off-by: Justin Paul <justin@jpaul.me>
2026-06-11 08:32:35 -04:00

901 lines
17 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
.f3 {
--female-color: rgb(196, 138, 146);
--male-color: rgb(120, 159, 172);
--genderless-color: lightgray;
--background-color: rgb(33, 33, 33);
--text-color: #fff;
font-family: 'Roboto', sans-serif;
}
.f3 * {
box-sizing: border-box;
}
.f3 .cursor-pointer {
cursor: pointer;
}
.f3 svg.main_svg {
width: 100%;
height: 100%;
}
.f3 svg.main_svg text {
fill: currentColor;
}
.f3 rect.card-female, .f3 .card-female .card-body-rect, .f3 .card-female .text-overflow-mask {
fill: var(--female-color);
}
.f3 rect.card-male, .f3 .card-male .card-body-rect, .f3 .card-male .text-overflow-mask {
fill: var(--male-color);
}
.f3 .card-genderless .card-body-rect, .f3 .card-genderless .text-overflow-mask {
fill: var(--genderless-color);
}
.f3 .card_add .card-body-rect {
fill: #3b5560;
stroke-width: 4px;
stroke: #fff;
cursor: pointer;
}
.f3 g.card_add text {
fill: #fff;
}
.f3 .card-main-outline {
stroke: currentColor;
stroke-width: 3px;
}
.f3 .card_family_tree rect {
transition: 0.3s;
}
.f3 .card_family_tree:hover rect {
transform: scale(1.1);
}
.f3 .card_add_relative {
cursor: pointer;
color: #fff;
transition: 0.3s;
}
.f3 .card_add_relative circle {
fill: rgba(0, 0, 0, 0);
}
.f3 .card_add_relative:hover {
color: black;
}
.f3 .card_edit.pencil_icon {
color: #fff;
transition: 0.3s;
}
.f3 .card_edit.pencil_icon:hover {
color: black;
}
.f3 .card_break_link, .f3 .link_upper, .f3 .link_lower, .f3 .link_particles {
transform-origin: 50% 50%;
transition: 1s;
}
.f3 .card_break_link {
color: #fff;
}
.f3 .card_break_link.closed .link_upper {
transform: translate(-140.5px, 655.6px);
}
.f3 .card_break_link.closed .link_upper g {
transform: rotate(-58deg);
}
.f3 .card_break_link.closed .link_particles {
transform: scale(0);
}
.f3 .input-field input {
height: 2.5rem !important;
}
.f3 .input-field > label:not(.label-icon).active {
-webkit-transform: translateY(-8px) scale(0.8);
transform: translateY(-8px) scale(0.8);
}
.f3.f3-cont {
width:100%;
height:900px;
max-height:70vh;
background-color: var(--background-color);
color: var(--text-color);
}
.f3 {
position: relative;
display: flex;
}
/* form-info */
.f3-form input[type="text"],
.f3-form textarea,
.f3-form select {
width: 100%;
padding: 8px 12px;
margin: 8px 0;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box;
font-size: 14px;
background: var(--background-color);
color: currentColor;
}
.f3-form input[type="text"]:focus,
.f3-form textarea:focus,
.f3-form select:focus {
box-shadow: 0 0 5px rgba(76, 175, 80, 0.2);
}
.f3-form button {
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
margin: 10px 0;
transition: background-color 0.3s ease-in-out, border-color 0.3s ease-in-out, color 0.3s ease-in-out;
}
.f3-form button[type="submit"] {
background-color: #4CAF50;
color: white;
}
.f3-cancel-btn {
background-color: #ccc;
}
.f3-form .f3-delete-btn {
background-color: transparent;
border: 1px solid #f44336;
color: #f44336;
width: 100%;
padding: 5px 10px;
}
.f3-delete-btn:hover {
background-color: #da190b;
border-color: #da190b;
color: #fff;
}
.f3-delete-btn:disabled {
opacity: 0.5;
background-color: transparent;
color: #f44336;
cursor: not-allowed;
}
.f3-form .f3-remove-relative-btn {
background-color: transparent;
border: 1px solid currentColor;
color: currentColor;
width: 100%;
padding: 5px 10px;
}
.f3-remove-relative-btn:hover, .f3-remove-relative-btn.active {
background-color: var(--text-color);
border-color: var(--text-color);
color: var(--background-color);
}
.f3-radio-group {
margin: 15px 0;
}
.f3-radio-group label {
margin-right: 15px;
cursor: pointer;
}
.f3-radio-group input[type="radio"] {
margin-right: 5px;
}
.f3-info-field-label, .f3-form-field label {
font-weight: bold;
font-size: 12px;
display: block;
opacity: 0.8;
}
.f3-info-field-value {
font-weight: normal;
display: block;
border: none;
outline: none;
border-bottom: 1px solid rgba(255,255,255,0.2);
padding-bottom: 1px;
margin-bottom: 10px;
min-height: 18px;
}
.f3-form-buttons {
text-align: right;
}
.f3-form-title {
text-align: center;
}
.f3-form.non-editable .f3-form-buttons,
.f3-form.non-editable .f3-delete-btn,
.f3-form.non-editable .f3-remove-relative-btn,
.f3-form.non-editable .f3-link-existing-relative {
display: none;
}
.f3-close-btn {
cursor: pointer;
position: absolute;
left: 10px;
top: 8px;
font-size: 30px;
color: var(--text-color);
}
.f3-edit-btn {
position: relative;
top: -1px;
width: 24px;
height: 24px;
cursor: pointer;
display: inline-block;
}
.f3-add-relative-btn {
cursor: pointer;
width: 27px;
height: 27px;
margin-right: 5px;
display: inline-block;
}
/* card-html */
.f3 div.card {
cursor: pointer;
color: var(--text-color);
position: relative;
line-height: 1.2;
}
.f3 div.card-image-circle {
border-radius: 50%;
padding: 5px;
width: 90px;
height: 90px;
}
.f3 div.card-image-circle div.card-label {
position: absolute;
bottom: -10px;
left: 50%;
transform: translate(-50%, 50%);
max-width: 150%;
min-height: 22px;
text-align: center;
background-color: rgba(0, 0, 0, 0.5);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
border-radius: 3px;
padding: 0 5px;
}
.f3 div.card-image-circle img {
width: 100%;
height: 100%;
border-radius: 50%;
object-fit: cover;
}
.f3 div.card-image-circle svg {
width: 100%;
height: 100%;
padding: 5px;
border-radius: 50%;
object-fit: cover;
}
.f3 div.card-image-circle img {
width: 100%;
height: 100%;
border-radius: 50%;
object-fit: cover;
}
.f3 div.card-rect {
padding: 5px;
border-radius: 3px;
width: 120px;
min-height: 70px;
overflow: hidden;
text-align: center;
display: flex;
flex-direction: column;
justify-content: center;
}
.f3 div.card-image-rect {
width: 200px;
min-height: 70px;
display: flex;
align-items: center;
border-radius: 5px;
}
.f3 div.card-image-rect .person-icon {
height: 70px;
width: 70px;
object-fit: cover;
flex: 0 0 auto;
padding: 5px;
margin-right: 10px;
}
.f3 div.card-image-rect img {
height: 70px;
width: 70px;
object-fit: cover;
flex: 0 0 auto;
padding: 5px;
margin-right: 10px;
border-radius: 8px;
}
.f3 div.card-image-rect svg {
object-fit: cover;
width: 100%;
height: 100%;
padding: 5px;
border-radius: 7px;
}
.f3 div.card-image-rect div.card-label {
height: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: center;
}
.f3 div.mini-tree {
text-align: right;
position: absolute;
top: -15px;
right: -2px;
z-index: -1;
}
.f3 div.mini-tree svg {
width: 55px;
}
.f3 .f3-card-duplicate-tag {
position: absolute;
top: 2px;
right: 2px;
color: rgb(255, 251, 220);
background-color: rgba(255, 251, 220, 0);
border-radius: 50%;
padding: 2px 4px;
font-weight: 600;
cursor: pointer;
transition: color 0.2s ease-in-out, background-color 0.2s ease-in-out;
}
.f3 .f3-card-duplicate-tag:hover {
background-color: rgba(255, 251, 220, 0.9);
color: #000;
}
/* Click the ×N badge → every copy of that person flashes (see tree/page.tsx). */
@keyframes f3-card-flash {
0%, 100% { outline-color: rgba(160, 106, 66, 0); }
30%, 70% { outline-color: rgba(160, 106, 66, 1); }
}
.f3 .f3-card-flash .card-inner {
outline: 4px solid rgba(160, 106, 66, 1);
animation: f3-card-flash 0.55s ease-in-out 3;
}
.f3 .f3-card-duplicate-hover div.card-inner {
transform: translate(0, -2px);
outline: 4px solid rgb(255, 251, 220);
}
.f3 .f3-card-duplicate-hover .f3-card-duplicate-tag {
background-color: rgba(255, 251, 220, .8);
color: #000;
}
.f3 .f3-remove-relative-active .card {
background-color: var(--background-color);
}
.f3 .f3-remove-relative-active .card-inner {
transition: border 0.2s ease-in-out, opacity 0.2s ease-in-out, transform 0.2s ease-in-out;
opacity: .75;
}
.f3 .f3-remove-relative-active .card:hover .card-inner {
opacity: .25;
}
.f3 .f3-remove-relative-active .card-male.card-depth--1:hover .card-inner {
transform: translate(-8px, -8px);
}
.f3 .f3-remove-relative-active .card.card-female.card-depth--1:hover .card-inner {
transform: translate(8px, -8px);
}
.f3 .f3-remove-relative-active .card.card-female.card-depth-0:hover .card-inner {
transform: translate(8px, 0);
}
.f3 .f3-remove-relative-active .card.card-male.card-depth-0:hover .card-inner {
transform: translate(-8px, 0);
}
.f3 .f3-remove-relative-active .card.card-depth-1:hover .card-inner {
transform: translate(0, 8px);
}
.f3 .f3-remove-relative-active .card.card-main .card-inner {
transform: translate(0, 0)!important;
opacity: 1!important;
}
.f3 div.card > div {
transition: transform 0.2s ease-in-out;
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.8);
}
.f3 .card-inner {
outline: 0px solid rgba(255, 255, 255, 1);
transition: outline 0.5s ease-in-out;
}
.f3 div.card-female .card-inner, .f3 div.card-female .person-icon svg {
background-color: var(--female-color);
}
.f3 div.card-male .card-inner, .f3 div.card-male .person-icon svg {
background-color: var(--male-color);
}
.f3 div.card-genderless .card-inner, .f3 div.card-genderless .person-icon svg {
background-color: var(--genderless-color);
}
.f3 div.card-new-rel .card-inner, .f3 div.card-new-rel .person-icon svg {
background-color: var(--background-color);
}
.f3 div.card-to-add .card-inner {
background-color: var(--background-color);
border: 1px solid;
}
.f3 div.card-to-add .card-inner .card-label {
margin: 0 auto;
}
.f3 div.card-to-add .person-icon {
display: none;
}
.f3 div.card-new-rel .card-inner {
border-width: 1px;
border-style: dashed;
outline: 0px !important;
}
.f3 div.card-new-rel.card-female .card-inner, .f3 div.card-to-add.card-female .card-inner {
border-color: var(--female-color);
color: var(--female-color);
}
.f3 div.card-new-rel.card-male .card-inner, .f3 div.card-to-add.card-male .card-inner {
color: var(--male-color);
border-color: var(--male-color);
}
.f3 div.card-unknown .card-inner {
background-color: var(--background-color);
border: 1px solid;
}
.f3 div.card-unknown .card-inner .card-label {
margin: 0 auto;
}
.f3 div.card-unknown .person-icon {
display: none;
}
.f3 div.card-new-rel .card-inner {
border-width: 1px;
border-style: dashed;
outline: 0px !important;
}
.f3 div.card-new-rel.card-female .card-inner, .f3 div.card-unknown.card-female .card-inner {
border-color: var(--female-color);
color: var(--female-color);
}
.f3 div.card-new-rel.card-male .card-inner, .f3 div.card-unknown.card-male .card-inner {
color: var(--male-color);
border-color: var(--male-color);
}
.f3 div.card:hover > div {
transform: translate(0, -2px);
}
.f3 div.card-main .card-inner, .f3 div.card:hover .card-inner {
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.8);
}
.f3 div.card-main .card-inner {
outline: 4px solid rgba(220, 220, 220, 1);
}
.f3 div.card-inner.f3-path-to-main {
outline: 4px solid rgba(255, 255, 255, 1);
}
.f3 .link {
transition: stroke-width 0.2s ease-in-out;
/* Brand-aware connector lines: dark on the light paper, light on dark. The
library's default white stroke is invisible on the light theme. */
fill: none;
stroke: var(--line);
stroke-width: 1.5px;
}
.f3 .link.f3-path-to-main {
stroke-width: 4px;
}
.f3-form-cont {
position: relative;
z-index: 6;
right: 0;
top: 0;
width: 0;
height: 100%;
background-color: var(--background-color);
overflow: auto;
flex: 0 0 auto;
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.5);
}
.f3-form-cont.opened {
width: 350px;
}
.f3-form {
padding: 20px;
}
.f3-form hr {
border-style: solid;
border-width: thin 0 0 0;
opacity: 0.15;
}
.f3-nav-cont {
position: absolute;
top: 0;
left: 0;
width: 100%;
display: flex;
}
.f3-history-controls {
padding: 8px 5px 7px 9px;
display: inline-block;
position: relative;
z-index: 2;
}
.f3-back-button, .f3-forward-button {
width: 30px;
height: 30px;
transition: opacity 0.3s ease;
cursor: pointer;
display: inline-block;
background-color: transparent;
border: none;
margin-right: 10px;
color: currentColor;
}
.f3-history-controls svg {
height: 100%;
}
.f3-back-button.disabled, .f3-forward-button.disabled {
opacity: 0.5;
}
.f3-modal {
display: none;
position: absolute;
z-index: 10;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgb(0,0,0);
background-color: rgba(0,0,0,0.4);
}
.f3-modal-content {
position: relative;
background-color: var(--background-color);
margin: 15% auto;
padding: 20px;
border: 1px solid #888;
border-radius: 5px;
width: 500px;
max-width: 90%;
}
.f3-modal-close {
color: #aaa;
position: absolute;
right: 10px;
top: 7px;
font-size: 28px;
font-weight: bold;
}
.f3-modal-close:hover,
.f3-modal-close:focus {
color: black;
text-decoration: none;
cursor: pointer;
}
.f3-popup {
position: fixed;
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.8);
}
.f3-popup-content {
position: relative;
background-color: var(--background-color);
border: 1px solid #888;
border-radius: 5px;
overflow: hidden;
width: 100%;
height: 100%;
}
.f3-popup-nav {
height: 20px;
}
.f3-popup-content-inner {
width: 100%;
height: 100%;
}
.f3-popup-close {
color: #aaa;
position: absolute;
z-index: 4;
right: 6px;
top: 1px;
font-size: 28px;
font-weight: bold;
line-height: 1;
}
.f3-popup-close:hover,
.f3-popup-close:focus {
color: black;
text-decoration: none;
cursor: pointer;
}
.f3-btn {
position: relative;
cursor: pointer;
padding: 5px 10px;
overflow: hidden;
border-width: 0;
outline: none;
border-radius: 3px;
box-shadow: 0 1px 4px rgba(0, 0, 0, .6);
background-color: var(--text-color);
color: var(--background-color);
transition: background-color .3s;
font-size: 14px;
}
.f3-btn:hover, .f3-btn:focus {
background-color: var(--background-color);
color: var(--text-color);
}
.f3-female-bg {
background-color: var(--female-color);
}
.f3-male-bg {
background-color: var(--male-color);
}
.f3-genderless-bg {
background-color: var(--genderless-color);
}
.f3-female-color {
color: var(--female-color);
}
.f3-male-color {
color: var(--male-color);
}
.f3-genderless-color {
color: var(--genderless-color);
}
.f3-autocomplete-cont {
position: relative;
display: inline-block;
z-index: 2;
font-size: 14px;
width: 200px;
}
.f3-autocomplete input {
border: 1px solid rgba(255, 255, 255, 0.2);
background-color: var(--background-color);
color: var(--text-color);
padding: 10px;
width: 100%;
}
.f3-autocomplete input:focus {
outline: none;
}
.f3-autocomplete-toggle {
position: absolute;
right: 10px;
top: 10px;
cursor: pointer;
color: var(--text-color);
transition: color 0.3s ease-in-out;
width: 20px;
}
.f3-autocomplete-items {
border: 1px solid rgba(255, 255, 255, 0.2);
border-top: none;
overflow-y: auto;
max-height: 0;
background-color: var(--background-color);
transition: max-height 0.3s ease-in-out;
}
.f3-autocomplete.active .f3-autocomplete-items {
max-height: 300px;
}
.f3-autocomplete-item > div {
padding: 10px;
cursor: pointer;
background-color: var(--background-color);
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
transition: background-color 0.3s ease-in-out, color 0.3s ease-in-out;
}
.f3-autocomplete-item > div:hover, .f3-autocomplete-item.f3-selected > div {
background-color: var(--text-color);
color: var(--background-color);
}
.f3-autocomplete-active {
background-color: DodgerBlue !important;
color: #ffffff;
}
.f3-kinship-info {
padding: 10px 20px;
}
.f3-kinship-info .f3-info-field {
color:#b3b01e
}
.f3-kinship-info-icon {
cursor:pointer;
display:inline-block;
width:18px;
height:18px;
color:#04a4f4;
position:relative;
top:4px;
left:2px;
}
.f3-kinship-info .f3 {
width:100%;
height: 100%;
position:relative;
background-color:rgb(33,33,33);
color:#fff;
}
.f3 .f3-kinship-info .card-kinship-self {
min-height: 0px;
width: 60px;
height: 60px;
border-radius: 50%;
background-color: var(--background-color) !important;
border: solid 3px;
color: #437fae;
font-weight: bold;
}
.f3 .f3-kinship-info .card-kinship-self.f3-real-label {
width: 150px;
height: 50px;
border-radius: 50px;
}
.f3 .f3-kinship-info .card-kinship-rel {
min-height: 0px;
width: 150px;
height: 50px;
border-radius: 50px;
background-color: #1d3456 !important;
font-weight: bold;
}
.f3 .f3-kinship-info .card-kinship-default {
min-height: 0px;
width: 150px;
height: 50px;
border-radius: 50px;
background-color: var(--background-color) !important;
border: solid 1px;
}
.f3-kinship-labels-toggle {
position: absolute;
top: 0;
left: 0;
z-index: 10;
font-size: 12px;
}
.f3-kinship-labels-toggle label {
cursor: pointer;
color: #fff;
font-weight: bold;
text-align: center;
padding: 2px 5px;
}
.f3-kinship-labels-toggle input[type="checkbox"] {
cursor: pointer;
margin-left: 5px;
margin-right: 5px;
margin-top: 5px;
margin-bottom: 5px;
}