git-tm
Version:
An elegant visual Git history explorer with real-time commit comparison capabilities
1,260 lines (1,094 loc) • 21.9 kB
CSS
@font-face {
font-family: "Inter";
src: url("./fonts/InterVariable.woff2") format("woff2-variations");
font-weight: 100 900;
font-display: swap;
font-style: normal;
}
:root {
--glass-bg: rgba(255, 255, 255, 0.1);
--glass-border: rgba(255, 255, 255, 0.2);
--primary-blue: #2b85fd;
--secondary-blue: #1a73e8;
--accent-purple: #7c3aed;
--text-primary: #e2e8f0;
--text-secondary: #94a3b8;
--bg-dark: #0f172a;
--bg-card: rgba(30, 41, 59, 0.7);
}
body {
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
sans-serif;
margin: 0;
padding: 0;
background: var(--bg-dark);
color: var(--text-primary);
height: 100vh;
overflow: hidden;
background-image: radial-gradient(
at 40% 20%,
rgba(61, 64, 241, 0.271) 0px,
transparent 50%
),
radial-gradient(at 80% 0%, rgba(124, 58, 237, 0.15) 0px, transparent 50%),
radial-gradient(at 0% 50%, rgba(43, 133, 253, 0.15) 0px, transparent 50%);
background-attachment: fixed;
}
::-webkit-scrollbar,
::-webkit-scrollbar-track,
::-webkit-scrollbar-thumb,
::-webkit-scrollbar-thumb:hover {
display: none;
}
* {
scrollbar-width: none;
-ms-overflow-style: none;
}
#app {
height: 100vh;
max-width: 100vw;
margin: 0;
padding: 20px;
box-sizing: border-box;
display: flex;
flex-direction: column;
backdrop-filter: blur(20px);
}
header {
flex-shrink: 0;
background: var(--glass-bg);
padding: 15px;
border-radius: 8px;
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
border: 1px solid var(--glass-border);
backdrop-filter: blur(10px);
margin-bottom: 10px;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
}
header h1 {
margin: 0 0 10px 0;
}
#repo-info {
text-align: center;
color: var(--text-secondary);
}
.repo-details {
display: flex;
gap: 15px;
align-items: center;
justify-content: center;
margin-top: 10px;
}
.repo-name {
font-size: 1.1em;
font-weight: 600;
color: var(--text-primary);
}
.branch-name {
color: var(--text-secondary);
background: var(--bg-card);
padding: 4px 12px;
border-radius: 15px;
font-size: 0.9em;
}
.commit {
padding: 12px;
margin: 8px 0;
border-radius: 6px;
border: 1px solid var(--glass-border);
background: var(--bg-card);
display: grid;
grid-template-columns: 80px 1fr auto;
gap: 12px;
align-items: center;
cursor: pointer;
transition: all 0.2s;
color: var(--text-primary);
backdrop-filter: blur(10px);
position: relative;
overflow: visible;
z-index: 1;
}
.commit:hover {
background: rgba(30, 41, 59, 0.9);
transform: translateX(2px);
border: 1px solid var(--primary-blue);
z-index: 2;
}
.commit::before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(
45deg,
transparent,
rgba(255, 255, 255, 0.05),
transparent
);
transform: translateX(-100%);
transition: transform 0.6s;
}
.commit:hover::before {
transform: translateX(100%);
}
.commit.selected-first {
border: 1px solid var(--primary-blue);
background: rgba(43, 133, 253, 0.2);
}
.commit.selected-second {
border: 1px solid var(--accent-purple);
background: rgba(124, 58, 237, 0.2);
}
.commit-hash {
font-family: monospace;
color: var(--primary-blue);
}
.commit-message {
font-weight: 500;
}
.commit-author,
.commit-date {
color: var(--text-secondary);
}
.commit-date {
color: var(--text-secondary);
white-space: nowrap;
font-family: monospace;
min-width: 200px;
font-size: 0.9em;
display: flex;
align-items: center;
}
.commit-info {
display: flex;
align-items: center;
gap: 12px;
margin-left: auto;
position: relative;
z-index: 2;
}
#comparison-panel {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: var(--glass-bg);
padding: 15px 15px 25px 15px;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
z-index: 1000;
overflow: hidden;
backdrop-filter: blur(20px);
border-top: 1px solid var(--glass-border);
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
height: 75vh;
transform: translateY(calc(100% - 45px));
max-height: calc(100vh - 100px);
}
#comparison-panel.collapsed {
transform: translateY(calc(100% - 90px));
}
#comparison-panel:not(.collapsed) {
transform: translateY(0);
}
.diff-content {
margin-top: 15px;
flex: 1;
overflow-y: auto;
background: var(--bg-card);
padding: 15px;
border-radius: 6px;
font-family: "Fira Code", "Consolas", monospace;
line-height: 1.5;
font-size: 14px;
contain: content;
will-change: transform;
border: 1px solid var(--glass-border);
}
.diff-line {
display: flex;
font-family: monospace;
white-space: pre;
font-size: 12px;
line-height: 1.5;
margin: 0;
position: relative;
background: black;
}
.line-numbers {
user-select: none;
width: 80px;
min-width: 80px;
padding: 0 8px;
color: #6e7781;
background: #f6f8fa;
border-right: 1px solid #e1e4e8;
text-align: right;
display: flex;
}
.line-number {
width: 50%;
padding: 0 4px;
color: #6e7781;
}
.diff-line code {
padding: 0 16px;
flex: 1;
}
.diff-line::before {
content: attr(data-line-number);
display: inline-block;
width: 40px;
margin-right: 10px;
color: #6e7781;
user-select: none;
text-align: right;
}
.diff-line.addition {
background-color: #e6ffec;
border-left: 4px solid #2ea043;
}
.diff-line.deletion {
background-color: #ffebe9;
border-left: 4px solid #cf222e;
}
.diff-line:not(.addition):not(.deletion) {
color: #57606a;
background-color: #f8f9fa;
border-left: 4px solid transparent;
}
.diff-line:hover {
filter: brightness(97%);
}
.compare-button {
padding: 1.3em 2em;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 2px;
font-weight: 500;
color: #ffffff;
background-color: #31c423;
border: none;
border-radius: 45px;
box-shadow: 0px 8px 15px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease 0s;
cursor: pointer;
outline: none;
}
.compare-button:hover {
background-color: rgb(24, 169, 48);
box-shadow: 0px 15px 20px rgba(46, 229, 157, 0.4);
color: #fff;
transform: translateY(-2px);
}
.compare-button:active {
transform: translateY(-1px);
}
.clear-button {
padding: 1.3em 2em;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 2px;
font-weight: 500;
color: #ffffff;
background: rgba(220, 53, 69, 0.8);
border: none;
border-radius: 45px;
box-shadow: 0px 8px 15px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease 0s;
cursor: pointer;
outline: none;
backdrop-filter: blur(10px);
}
.clear-button:hover {
background-color: #ad2936;
box-shadow: 0px 15px 20px rgba(229, 46, 46, 0.4);
color: #fff;
transform: translateY(-2px);
}
.clear-button:active {
transform: translateY(-1px);
}
.layout {
flex: 1;
display: grid;
grid-template-columns: 250px 1fr;
gap: 20px;
height: calc(100vh - 260px);
overflow: hidden;
margin-bottom: 60px;
}
.sidebar {
background: var(--glass-bg);
padding: 20px;
overflow-x: hidden;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
height: calc(100vh - 292px);
width: 219px;
overflow-y: auto;
border: 1px solid var(--glass-border);
backdrop-filter: blur(10px);
}
.sidebar::-webkit-scrollbar,
.commits-container::-webkit-scrollbar {
display: block;
width: 8px;
}
.sidebar::-webkit-scrollbar-track,
.commits-container::-webkit-scrollbar-track {
background: transparent;
}
.sidebar::-webkit-scrollbar-thumb,
.commits-container::-webkit-scrollbar-thumb {
background-color: var(--glass-border);
border-radius: 4px;
border: 2px solid transparent;
background-clip: padding-box;
}
.sidebar::-webkit-scrollbar-thumb:hover,
.commits-container::-webkit-scrollbar-thumb:hover {
background-color: var(--primary-blue);
}
.sidebar,
.commits-container {
scrollbar-width: thin;
scrollbar-color: var(--glass-border) transparent;
}
.commits-container {
margin-top: 10px;
overflow-y: auto;
overflow-x: hidden;
max-height: calc(100vh - 360px);
padding-right: 16px;
padding-bottom: 20px;
position: relative;
z-index: 1;
}
.branch-list {
margin-top: 10px;
}
.branch-item {
padding: 8px 12px;
margin: 4px 0;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
color: var(--text-primary);
}
.branch-item:hover {
background: rgba(43, 133, 253, 0.2);
}
.branch-item.active {
background: var(--primary-blue);
color: white;
}
.branch-section {
margin-bottom: 20px;
}
.branch-section h3 {
font-size: 0.9em;
color: var(--text-secondary);
margin-bottom: 8px;
padding-bottom: 4px;
border-bottom: 1px solid #eee;
}
.branch-item.remote {
color: var(--text-secondary);
padding-left: 20px;
position: relative;
}
.branch-item.remote::before {
content: "⇋";
position: absolute;
left: 6px;
color: var(--text-secondary);
}
.timeline-section {
background: var(--glass-bg);
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
height: calc(100vh - 292px);
margin-bottom: 0;
border: 1px solid var(--glass-border);
backdrop-filter: blur(10px);
position: relative;
background: linear-gradient(
to bottom,
var(--glass-bg),
rgba(30, 41, 59, 0.8)
);
display: flex;
flex-direction: column;
}
.commits-container {
margin-top: 10px;
overflow-y: auto;
overflow-x: hidden;
max-height: calc(100vh - 360px);
padding-right: 16px;
padding-bottom: 20px;
position: relative;
z-index: 1;
}
.comparison-header {
position: relative;
padding: 5px 15px;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
}
.commit-selectors {
display: flex;
gap: 15px;
align-items: center;
margin-top: 2px;
}
.select-group {
display: flex;
align-items: center;
gap: 8px;
}
.commit-select {
padding: 6px 12px;
border: 1px solid var(--glass-border);
border-radius: 6px;
width: 300px;
font-family: monospace;
background: var(--bg-card);
color: var(--text-primary);
backdrop-filter: blur(10px);
}
.commit-select option {
background: var(--bg-dark);
color: var(--text-primary);
padding: 8px 12px;
}
.commit-select option:hover,
.commit-select option:focus,
.commit-select option:active {
background: var(--bg-dark) ;
color: var(--text-primary) ;
box-shadow: none ;
}
.commit-select option:checked {
background: var(--primary-blue);
color: white;
}
.branch-info {
display: flex;
gap: 20px;
margin-top: 10px;
color: var(--text-secondary);
font-size: 0.9em;
}
.branch-info div {
background: var(--bg-card);
padding: 4px 12px;
border-radius: 15px;
}
h2,
h3 {
margin: 0;
color: #ffffff;
}
@media (max-width: 768px) {
.layout {
grid-template-columns: 1fr;
height: calc(100vh - 160px);
}
body {
padding: 0;
}
.commit {
grid-template-columns: 1fr;
gap: 8px;
}
.commit-selectors {
flex-direction: column;
align-items: stretch;
}
.select-group {
flex-direction: column;
align-items: stretch;
}
.commit-select {
width: 100%;
}
.timeline-section {
max-height: none;
}
#comparison-panel {
height: 45vh;
}
.diff-content {
height: calc(45vh - 140px);
}
}
@media (min-width: 769px) and (max-width: 1024px) {
.layout {
grid-template-columns: 200px 1fr;
}
.commit {
grid-template-columns: 70px 1fr 120px;
}
}
.dropdown {
position: relative;
cursor: pointer;
width: 45px;
height: 45px;
background: var(--primary-blue);
box-shadow: 0 8px 32px rgba(43, 133, 253, 0.3);
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
margin-left: 10px;
}
.dropdown input {
position: absolute;
opacity: 0;
width: 100%;
height: 100%;
cursor: pointer;
z-index: 2;
}
.dropdown svg {
width: 24px;
height: 24px;
fill: white;
transition: transform 0.3s ease;
z-index: 1;
transform: rotate(180deg);
}
#comparison-panel:not(.collapsed) .dropdown svg {
transform: rotate(0deg);
}
.diff-file {
margin-bottom: 20px;
border: 1px solid var(--glass-border);
border-radius: 6px;
overflow: hidden;
background: var(--bg-card);
}
.diff-file-header {
background: rgba(30, 41, 59, 0.9);
padding: 10px 16px;
display: flex;
align-items: center;
gap: 10px;
cursor: pointer;
user-select: none;
border-bottom: 1px solid var(--glass-border);
}
.file-status {
font-family: monospace;
font-weight: bold;
font-size: 14px;
min-width: 24px;
}
.diff-file-name {
flex-grow: 1;
font-family: monospace;
font-weight: 600;
color: #ffffff;
}
.diff-stats {
margin-right: 16px;
}
.diff-added {
color: #28a745;
margin-right: 8px;
}
.diff-removed {
color: #d73a49;
}
.diff-collapse-icon {
transition: transform 0.2s ease;
}
.diff-file.collapsed .diff-collapse-icon {
transform: rotate(-90deg);
}
.diff-file.collapsed .diff-file-content {
display: none;
}
.diff-file-content {
background: #1a2b4b;
overflow-x: auto;
}
.diff-hunk-header {
color: #e2e8f0;
background: #243660;
padding: 8px 16px;
font-family: monospace;
font-size: 12px;
border-bottom: 1px solid #334873;
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
}
.diff-hunk-header::before {
content: "•";
color: #6e7781;
}
.diff-line.addition {
background: #e6ffec;
}
.diff-line.deletion {
background: #ffebe9;
}
.diff-line.addition code {
color: #1a7f37;
}
.diff-line.deletion code {
color: #cf222e;
}
.diff-line .fullscreen-loader,
.terminal-loader {
display: none;
}
#loader-container {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgb(4, 35, 78);
display: none;
justify-content: center;
align-items: center;
z-index: 9999;
opacity: 1;
transition: opacity 0.5s ease-out;
}
#loader-container.fade-out {
opacity: 0;
}
.loader {
--path: #e84d31;
--dot: #ffffff;
--duration: 3s;
width: 44px;
height: 44px;
position: relative;
}
.loader:before {
content: "";
width: 6px;
height: 6px;
border-radius: 50%;
position: absolute;
display: block;
background: var(--dot);
top: 37px;
left: 19px;
transform: translate(-18px, -18px);
animation: dotRect var(--duration) cubic-bezier(0.785, 0.135, 0.15, 0.86)
infinite;
}
.loader svg {
display: block;
width: 100%;
height: 100%;
}
.loader svg rect,
.loader svg polygon,
.loader svg circle {
fill: none;
stroke: var(--path);
stroke-width: 10px;
stroke-linejoin: round;
stroke-linecap: round;
}
.loader svg polygon {
stroke-dasharray: 145 76 145 76;
stroke-dashoffset: 0;
animation: pathTriangle var(--duration) cubic-bezier(0.785, 0.135, 0.15, 0.86)
infinite;
}
.loader svg rect {
stroke-dasharray: 192 64 192 64;
stroke-dashoffset: 0;
animation: pathRect 3s cubic-bezier(0.785, 0.135, 0.15, 0.86) infinite;
}
.loader svg circle {
stroke-dasharray: 150 50 150 50;
stroke-dashoffset: 75;
animation: pathCircle var(--duration) cubic-bezier(0.785, 0.135, 0.15, 0.86)
infinite;
}
.loader.triangle {
width: 48px;
}
.loader.triangle:before {
left: 21px;
transform: translate(-10px, -18px);
animation: dotTriangle var(--duration) cubic-bezier(0.785, 0.135, 0.15, 0.86)
infinite;
}
@keyframes pathTriangle {
33% {
stroke-dashoffset: 74;
}
66% {
stroke-dashoffset: 147;
}
100% {
stroke-dashoffset: 221;
}
}
@keyframes dotTriangle {
33% {
transform: translate(0, 0);
}
66% {
transform: translate(10px, -18px);
}
100% {
transform: translate(-10px, -18px);
}
}
@keyframes pathRect {
25% {
stroke-dashoffset: 64;
}
50% {
stroke-dashoffset: 128;
}
75% {
stroke-dashoffset: 192;
}
100% {
stroke-dashoffset: 256;
}
}
@keyframes dotRect {
25% {
transform: translate(0, 0);
}
50% {
transform: translate(18px, -18px);
}
75% {
transform: translate(0, -36px);
}
100% {
transform: translate(-18px, -18px);
}
}
@keyframes pathCircle {
25% {
stroke-dashoffset: 125;
}
50% {
stroke-dashoffset: 175;
}
75% {
stroke-dashoffset: 225;
}
100% {
stroke-dashoffset: 275;
}
}
.loader {
display: inline-block;
margin: 0 16px;
}
.bento-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
padding: 20px;
}
.bento-item {
background: var(--glass-bg);
border-radius: 16px;
padding: 24px;
border: 1px solid var(--glass-border);
backdrop-filter: blur(10px);
transition: all 0.3s ease;
}
.bento-item:hover {
transform: translateY(-5px);
box-shadow: 0 8px 32px rgba(43, 133, 253, 0.2);
border-color: var(--primary-blue);
}
.bento-item.featured {
grid-column: span 2;
grid-row: span 2;
}
.glass-card {
background: linear-gradient(
135deg,
rgba(255, 255, 255, 0.1),
rgba(255, 255, 255, 0.05)
);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid var(--glass-border);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
}
.fade-out {
opacity: 0 ;
}
.binary-message {
padding: 20px;
text-align: center;
color: var(--text-secondary);
background: var(--bg-dark);
border-radius: 4px;
margin: 10px;
}
.diff-file-info {
color: var(--text-secondary);
margin-right: 16px;
padding: 2px 8px;
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
font-size: 0.9em;
}
.card {
width: 330px;
height: 80px;
border-radius: 8px;
box-sizing: border-box;
padding: 10px 15px;
background-color: #ffffff;
box-shadow: rgba(149, 157, 165, 0.2) 0px 8px 24px;
position: relative;
overflow: hidden;
display: flex;
align-items: center;
justify-content: space-around;
gap: 15px;
}
.wave {
position: absolute;
transform: rotate(90deg);
left: -31px;
top: 32px;
width: 80px;
fill: #ffa30d3a;
}
.icon-container {
width: 35px;
height: 35px;
display: flex;
justify-content: center;
align-items: center;
background-color: #ffa30d48;
border-radius: 50%;
margin-left: 8px;
}
.icon {
width: 17px;
height: 17px;
color: #db970e;
}
.message-text-container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
flex-grow: 1;
}
.message-text,
.sub-text {
margin: 0;
cursor: default;
}
.message-text {
color: #db970e;
font-size: 17px;
font-weight: 700;
}
.sub-text {
font-size: 14px;
color: #555;
}
.cross-icon {
width: 18px;
height: 18px;
color: #555;
cursor: pointer;
}
.card.toast {
width: 330px;
height: 80px;
border-radius: 8px;
box-sizing: border-box;
padding: 10px 15px;
background-color: #ffffff;
box-shadow: rgba(149, 157, 165, 0.2) 0px 8px 24px;
position: relative;
overflow: hidden;
display: flex;
align-items: center;
justify-content: space-around;
gap: 15px;
opacity: 0;
transform: translateY(-20px);
transition: all 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55);
margin-bottom: 10px;
}
.card.toast.show {
opacity: 1;
transform: translateY(0);
}
.card.toast.hide {
opacity: 0;
transform: translateY(-20px);
}
#toast-container {
position: fixed;
top: 10px;
left: 50%;
transform: translateX(-50%);
z-index: 9999;
display: flex;
flex-direction: column;
align-items: center;
pointer-events: none;
}
.wave {
position: absolute;
transform: rotate(90deg);
left: -31px;
top: 32px;
width: 80px;
fill: #ffa30d3a;
}
.icon-container {
width: 35px;
height: 35px;
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
margin-left: 8px;
}
.icon {
width: 17px;
height: 17px;
}
.message-text-container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
flex-grow: 1;
}
.message-text {
margin: 0;
cursor: default;
font-size: 17px;
font-weight: 700;
}
.cross-icon {
width: 18px;
height: 18px;
color: #555;
cursor: pointer;
}
.commit-author-container {
position: relative;
display: flex;
align-items: center;
cursor: pointer;
}
.author-avatar {
width: 32px;
height: 32px;
border-radius: 50%;
transition: transform 0.2s ease;
border: 2px solid var(--glass-border);
}
.author-avatar:hover {
transform: scale(1.1);
border-color: var(--primary-blue);
}
.author-tooltip {
position: absolute;
right: 0;
top: calc(100% + 5px);
background: var(--bg-dark);
border: 1px solid var(--glass-border);
border-radius: 8px;
padding: 16px;
min-width: 250px;
backdrop-filter: blur(10px);
z-index: 100000;
opacity: 0;
visibility: hidden;
transform: translateY(-10px);
transition: all 0.2s ease;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
}
.commit-author-container:hover .author-tooltip {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
.author-tooltip-content {
display: flex;
flex-direction: column;
gap: 8px;
}
.author-name {
font-weight: 600;
color: var(--text-primary);
}
.author-email {
color: var(--text-secondary);
font-size: 0.9em;
}
.github-link {
color: var(--primary-blue);
text-decoration: none;
display: flex;
align-items: center;
gap: 4px;
font-size: 0.9em;
margin-top: 4px;
}
.github-link:hover {
text-decoration: underline;
}
.app-logo {
width: 50px;
height: 50px;
/* object-fit: contain; */
display: inline-block;
margin: 0;
vertical-align: middle;
}
.title-container {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 10px;
flex-direction: row;
}
.title-container h1 {
margin: 0;
display: inline-flex;
align-items: center;
}
.app-logo {
width: 40px;
height: 40px;
object-fit: contain;
display: block;
}