foxygit / foxygit Log in
A tiny read-only git web frontend — browse bare repos with just PHP and git, no database, no framework.
commits tags

/assets/base.css · 19.87 KB

raw
/* foxygit — structure only, no colors. All colors come from a theme file
   in themes/*.css via CSS custom properties (--bg, --fg, --dim, --acc,
   --line, --surface, --pre-bg, --add, --del, --hunk, --font-ui). */

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font: 14px/1.5 var(--font-ui);
  -webkit-font-smoothing: antialiased;
}

a { color: var(--acc); text-decoration: none; }
a:hover { text-decoration: underline; }

/* code/hashes/diffs always render monospace, regardless of --font-ui */
pre, code, .mono, .hash {
  font-family: ui-monospace, "JetBrains Mono", Menlo, Consolas, monospace;
}

/* middle, not text-bottom: table cells set their own line-height, and
   text-bottom pushed icons visibly below the row's text baseline. */
.icon { fill: currentColor; vertical-align: middle; flex: none; }

/* the shared centred column — header, tabs and main all line up on it */
.inner { max-width: 1100px; margin: 0 auto; padding: 0 24px; width: 100%; }

/* ---------------------------------------------------------------- header */

header {
  border-bottom: 1px solid var(--line);
  background: var(--surface);
  padding: 12px 0;
}
header .inner { display: flex; align-items: center; flex-wrap: wrap; gap: 4px 10px; }
header .brand { color: var(--fg); font-weight: 600; font-size: 16px; display: inline-flex; align-items: center; gap: 8px; }
header .brand .icon { color: var(--dim); }
header .brand:hover { text-decoration: none; }
header .path { color: var(--dim); display: inline-flex; align-items: center; gap: 6px; font-size: 16px; }
header .path a { color: var(--fg); font-weight: 600; }
header .spacer { flex: 1; }

/* also used on a <button> (the header's log-out form), so it has to unset the
   background the button rule further down would otherwise give it — every
   icon button in the header sits flush until hovered. */
.icon-btn {
  color: var(--dim);
  background: none;
  border: 1px solid transparent;
  border-radius: 6px;
  padding: 5px;
  display: inline-flex;
  line-height: 0;
  cursor: pointer;
}
.icon-btn:hover { color: var(--fg); background: var(--bg); border-color: var(--line); text-decoration: none; }

main { padding: 24px 0 0; }

/* ----------------------------------------------------------------- tabs */

nav.tabs { border-bottom: 1px solid var(--line); }
nav.tabs .inner { display: flex; gap: 4px; }
nav.tabs a {
  color: var(--dim);
  padding: 10px 12px;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
nav.tabs a:hover { color: var(--fg); text-decoration: none; }
nav.tabs a.on { color: var(--fg); font-weight: 600; border-bottom-color: var(--acc); }

/* --------------------------------------------------------------- subnav:
   branch pills + clone URL + feed links, the row above the content. */

.repo-subnav {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 16px;
}
.repo-subnav .branches { display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.repo-subnav .branches .icon { color: var(--dim); }
.clone-box {
  background: var(--pre-bg);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 5px 10px;
  display: inline-flex;
  gap: 8px;
  align-items: center;
  font-size: 12px;
  max-width: 100%;
  overflow: hidden;
  color: var(--dim);
  font-family: inherit;
  cursor: pointer;
}
.clone-box:hover { border-color: var(--acc); color: var(--fg); }
.clone-box code { color: inherit; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.clone-box .done-icon { display: none; }
.clone-box.copied { color: var(--add); border-color: var(--add); }
.clone-box.copied .copy-icon { display: none; }
.clone-box.copied .done-icon { display: inline-flex; }
.feeds { margin-left: auto; color: var(--dim); font-size: 12px; display: flex; gap: 10px; align-items: center; }
.feeds a { color: var(--dim); }
.feeds a:hover { color: var(--fg); }

/* ---------------------------------------------------------------- tables */

table { width: 100%; border-collapse: collapse; }
td, th { text-align: left; padding: 8px 16px 8px 0; vertical-align: middle; }
th { color: var(--dim); font-weight: 400; font-size: 12px; }
tr:hover td { background: var(--surface); }

.desc { color: var(--dim); }
.num { text-align: right; white-space: nowrap; }

/* ------------------------------------------------------------------ .box:
   the card wrapper used around file/ref/log tables and the README. */

.box {
  border: 1px solid var(--line);
  border-radius: 6px;
  overflow: hidden;
  margin-bottom: 16px;
}
.box table { margin: 0; }
.box td, .box th { padding-left: 16px; }
.box tr { border-top: 1px solid var(--line); }
.box tr:first-child { border-top: 0; }
.box.about { padding: 14px 16px; color: var(--fg); }
.box thead th {
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  padding-top: 10px;
  padding-bottom: 10px;
}
.box thead tr:hover td { background: none; }

/* file tree: type icon, name, right-aligned size.
   The icon is display:block so it isn't treated as inline text — as an inline
   element an SVG sits on the baseline and reserves descender space beneath it,
   which is what pushed it below the filename. The cell centres it instead. */
.tree td.mode { width: 1%; padding-right: 0; color: var(--dim); }
.tree td.mode .icon { display: block; }
.tree td.num { width: 1%; color: var(--dim); font-size: 12px; }
.tree td.commit-msg { max-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-size: 13px; }
.tree td.commit-msg a { color: inherit; }
.tree td.commit-msg a:hover { color: var(--acc); }
.tree tbody a { color: var(--fg); }
.tree tbody a:hover { color: var(--acc); }
.box-header {
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  padding: 10px 16px;
  font-size: 12px;
  color: var(--dim);
  display: flex;
  align-items: center;
  gap: 8px;
}

/* --------------------------------------------------------------- pills:
   commit hashes, branch names — small rounded chips on an inset bg. */

.pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--pre-bg);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 2px 8px;
  font-size: 12px;
  color: var(--dim);
}
a.pill:hover { text-decoration: none; border-color: var(--acc); color: var(--fg); }
a.pill.on { color: var(--fg); border-color: var(--acc); }

/* ----------------------------------------------------------------- diff */

pre {
  background: var(--pre-bg);
  border: 1px solid var(--line);
  padding: 12px;
  overflow: auto;
  border-radius: 6px;
}
.add { color: var(--add); }
.del { color: var(--del); }
.hunk { color: var(--hunk); }

/* --------------------------------------------------------------- readme */

.readme .box-header { color: var(--fg); font-weight: 600; }
.readme .body { padding: 24px 32px; }
.readme h1, .readme h2, .readme h3, .readme h4, .readme h5, .readme h6 {
  color: var(--fg); margin: 24px 0 12px; scroll-margin-top: 16px;
}
.readme h1 { border-bottom: 1px solid var(--line); padding-bottom: .3em; font-size: 26px; }
.readme h2 { border-bottom: 1px solid var(--line); padding-bottom: .3em; font-size: 20px; }
.readme h1:first-child, .readme h2:first-child { margin-top: 0; }
.readme p { margin: 12px 0; }
.readme ul, .readme ol { margin: 12px 0; padding-left: 22px; }
.readme li { margin: 4px 0; }
.readme li > ul, .readme li > ol { margin: 4px 0; }
.readme code { background: var(--surface); padding: 2px 6px; border-radius: 6px; font-size: 85%; }
.readme pre { background: var(--pre-bg); }
.readme pre code { background: none; padding: 0; font-size: 100%; }
.readme img { max-width: 100%; height: auto; }
.readme del { color: var(--dim); }
.readme hr { border: 0; border-top: 1px solid var(--line); margin: 24px 0; }
.readme blockquote {
  margin: 12px 0;
  padding: 1px 1em;
  color: var(--dim);
  border-left: .25em solid var(--line);
}
.readme blockquote > *:first-child { margin-top: 0; }
.readme blockquote > *:last-child { margin-bottom: 0; }
.readme table { display: block; width: max-content; max-width: 100%; overflow: auto; margin: 12px 0; border-collapse: collapse; }
.readme table th, .readme table td { border: 1px solid var(--line); padding: 6px 12px; }
.readme table tr:nth-child(2n) td { background: var(--surface); }
.license-line { color: var(--dim); font-size: 12px; margin: 0 0 16px; }

/* --------------------------------------------------------------- blob view */

.blob-toolbar { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; }
.blob-toolbar .desc { margin: 0; }
.blob-toolbar .spacer { flex: 1; }
.img-frame { padding: 32px; text-align: center; }
.img-frame img { max-width: 100%; max-height: 70vh; border-radius: 4px; }

/* ------------------------------------------------------------- repo list

   Real <table> markup, restyled as a stack of bordered cards via CSS grid
   — keeps it a semantic table while looking like GitHub's repo list. */

#repo-list { display: block; }
#repo-list tbody, #repo-list tr { display: block; }
#repo-list tr {
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 16px;
  margin-bottom: 12px;
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 4px 16px;
}
#repo-list tr:hover td { background: none; }
#repo-list td { padding: 0; border: 0; }
#repo-list td:nth-child(1) { grid-row: 1; font-size: 16px; font-weight: 600; }
#repo-list td:nth-child(2) { grid-column: 1 / 2; grid-row: 2; color: var(--dim); font-size: 13px; }
#repo-list td:nth-child(3) { grid-column: 2 / 3; grid-row: 1; color: var(--dim); font-size: 12px; white-space: nowrap; }
#repo-list td:nth-child(4) { grid-column: 1 / -1; grid-row: 3; }

/* the language bar: a row of proportionally-sized colored segments, GitHub-style,
   plus a wrapping text legend (colored dot + name + percentage) underneath. */
.lang-bar {
  display: flex;
  width: 25%;
  height: 6px;
  border-radius: 3px;
  overflow: hidden;
  background: var(--surface);
  margin-top: 8px;
}
.lang-bar span { display: block; height: 100%; }
.lang-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 14px;
  margin-top: 6px;
  font-size: 12px;
  color: var(--dim);
}
.lang-legend-item { display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; }
.lang-legend-item i { width: 9px; height: 9px; display: inline-block; flex: none; }

/* ------------------------------------------------------------------ log */

.log-date {
  padding: 10px 16px;
  background: var(--surface);
  color: var(--dim);
  font-size: 12px;
  border-top: 1px solid var(--line);
}
.log-date:first-child { border-top: 0; }
.box table td.subject { font-weight: 500; }

/* --------------------------------------------------------------- footer */

footer {
  color: var(--dim);
  border-top: 1px solid var(--line);
  margin-top: 40px;
  padding: 24px 0;
  font-size: 12px;
}
footer .inner { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }

.theme-switch { display: inline-flex; align-items: center; gap: 6px; }
.theme-switch select {
  background: var(--surface);
  color: var(--fg);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 3px 6px;
  font: inherit;
  font-size: 12px;
}

/* ------------------------------------------------------------ auth nav:
   the account / admin / log-out controls. flex-basis:100% inside the
   header's wrapping .inner puts them on their OWN row under the brand and
   icon buttons, rather than competing for the corner — squeezed in beside
   the theme and help icons they wrapped raggedly, with the log-out button
   dropping under the username. nowrap keeps the three together on that row.
   Colours are set explicitly (not via --pre-bg, whose job is code insets)
   so the chips read the same in every theme. */

.nav-auth {
  flex-basis: 100%;
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  justify-content: flex-end;
  gap: 10px;
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--line);
}
.nav-auth form { display: inline-flex; margin: 0; }

/* one shared look for all three, with a fixed height so they line up exactly */
.nav-btn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 30px;
  padding: 0 12px;
  background: var(--surface);
  color: var(--fg);
  border: 1px solid var(--line);
  border-radius: 6px;
  font: inherit;
  font-size: 13px;
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  transition: border-color .12s, color .12s;
}
.nav-btn:hover { border-color: var(--acc); color: var(--acc); text-decoration: none; }
.nav-btn:focus-visible {
  outline: none;
  border-color: var(--acc);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--acc) 15%, transparent);
}
.nav-btn .icon { color: var(--dim); }
.nav-btn:hover .icon { color: inherit; }

.nav-sep { width: 1px; height: 18px; background: var(--line); flex: none; }

/* ----------------------------------------------------------- page titles:
   setup/login/account/admin are the only pages with a bare <h1> — every
   other view relies on the header/tab bar for context instead. Kept light
   (not a browser-default giant serif heading) so it sits at home next to
   the rest of the 14px UI; can hold a leading .icon (h1's flex+gap centre
   it against the text the same way box-header's icon does). */
h1 {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 20px;
  font-weight: 600;
  color: var(--fg);
  margin: 4px 0 20px;
}

/* --------------------------------------------------------------- forms:
   login/setup/account/admin — the only pages in foxygit with real forms. */

.form { max-width: 420px; }
.form-row { margin-bottom: 14px; }
.form-row:last-of-type { margin-bottom: 18px; }
.form-row label,
.form > label { display: block; margin-bottom: 5px; color: var(--dim); font-size: 12px; }

/* Capped rather than 100%: these forms sit inside full-width cards, and a
   text box stretched across the whole 1100px column looks broken. The auth
   pages (narrow, centred) opt back into full width further down. */
input[type="text"], input[type="password"] {
  width: 100%;
  max-width: 320px;
  background: var(--pre-bg);            /* inset, like .pill and .clone-box */
  color: var(--fg);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 7px 10px;
  font: inherit;
  font-size: 13px;
}
input[type="text"]::placeholder, input[type="password"]::placeholder { color: var(--dim); }
input[type="text"]:focus, input[type="password"]:focus {
  outline: none;
  border-color: var(--acc);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--acc) 15%, transparent);
}

/* input + its submit button side by side — the single-field "create" forms */
.field-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.field-row button { flex: none; }

/* one-line note under a field, explaining the rule the input enforces */
.field-hint { margin: 10px 0 0; font-size: 12px; }
.field-hint code { background: var(--pre-bg); border-radius: 4px; padding: 1px 5px; }

.check-row { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--fg); }
.check-row label { margin: 0; color: var(--fg); font-size: 13px; display: inline-flex; align-items: center; gap: 8px; }
input[type="checkbox"] { accent-color: var(--acc); width: 15px; height: 15px; margin: 0; flex: none; }

/* Buttons stay in the site's quiet register: thin border, inset fill, accent
   carried as *colour* rather than a saturated slab. --acc is a pale mint, so
   a solid fill of it reads as a glaring lozenge on a near-black page; a
   tinted ghost keeps the primary action obvious without shouting.
   (The plain background: is the fallback for anything without color-mix.) */
button, .btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  background: var(--surface);
  color: var(--fg);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 7px 14px;
  font: inherit;
  font-size: 13px;
  line-height: 1.4;
  cursor: pointer;
  transition: border-color .12s, background .12s, color .12s;
}
button:hover, .btn:hover { border-color: var(--acc); color: var(--acc); text-decoration: none; }
button:focus-visible, .btn:focus-visible {
  outline: none;
  border-color: var(--acc);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--acc) 15%, transparent);
}

button.primary, .btn.primary {
  color: var(--acc);
  font-weight: 600;
  background: var(--surface);
  background: color-mix(in srgb, var(--acc) 13%, var(--surface));
  border-color: var(--line);
  border-color: color-mix(in srgb, var(--acc) 45%, var(--line));
}
button.primary:hover, .btn.primary:hover {
  background: color-mix(in srgb, var(--acc) 22%, var(--surface));
  border-color: var(--acc);
  color: var(--acc);
}

button.danger, .btn.danger { color: var(--del); }
button.danger:hover, .btn.danger:hover {
  border-color: var(--del);
  background: color-mix(in srgb, var(--del) 14%, var(--surface));
  color: var(--del);
}

.inline-form { display: inline-flex; gap: 6px; align-items: center; }

.msg-error, .msg-ok {
  display: flex;
  gap: 10px;
  border-radius: 6px;
  padding: 12px 14px;
  margin-bottom: 16px;
  border: 1px solid;
}
.msg-error .icon, .msg-ok .icon { flex: none; margin-top: 1px; }
.msg-error { color: var(--del); background: var(--pre-bg); border-color: var(--del); }
.msg-ok { color: var(--add); background: var(--pre-bg); border-color: var(--add); }
.msg-error > div, .msg-ok > div { color: var(--fg); }
.msg-ok p:first-child { margin-top: 0; }
.msg-ok p:last-child { margin-bottom: 0; }

.key-reveal { margin: 10px 0; }
.key-reveal code {
  display: block;
  background: var(--pre-bg);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 8px 10px;
  word-break: break-all;
}

/* The "create" forms get their OWN card (box-header + form), rather than
   being tacked onto the bottom of the list they add to — so the box-header's
   own bottom rule is the only divider needed above them. */
.box .form {
  max-width: none;
  padding: 16px;
  margin: 0;
}
.box .form-row:last-of-type { margin-bottom: 14px; }
.box .form .field-row:last-child,
.box .form > label + .field-row { margin-bottom: 0; }

/* ------------------------------------------------------------ row-actions:
   the trash-icon disclosure used for delete-repo/delete-user — a plain
   <details>/<summary>, no JS, so the confirm field only appears once you've
   deliberately opened it instead of cluttering every row by default. */
.row-actions summary {
  list-style: none;
  display: inline-flex;
  color: var(--dim);
  border: 1px solid transparent;
  border-radius: 6px;
  padding: 5px;
  cursor: pointer;
}
.row-actions summary::-webkit-details-marker { display: none; }
.row-actions summary:hover { color: var(--del); background: var(--bg); border-color: var(--line); }
.row-actions[open] summary { color: var(--del); background: var(--bg); border-color: var(--line); }
.row-actions .confirm-delete {
  display: flex;
  gap: 6px;
  justify-content: flex-end;
  margin-top: 8px;
}
.row-actions .confirm-delete input[type="text"] { width: 210px; max-width: 210px; }

/* --------------------------------------------------------- auth pages:
   setup/login — a single centred card, the one place in foxygit that
   looks like a dedicated screen rather than a page under the header. */
.auth-page { max-width: 360px; margin: 56px auto 0; }
.auth-page h1 { justify-content: center; font-size: 22px; margin-bottom: 6px; }
.auth-page .lede { text-align: center; margin: 0 0 24px; }
.auth-page .box { padding: 26px 26px 4px; background: var(--surface); }
.auth-page .form { max-width: none; padding: 0; background: none; border-top: 0; }
/* narrow, centred column — here full-width fields are the right call */
.auth-page input[type="text"], .auth-page input[type="password"] { max-width: none; }
.auth-page button.primary { width: 100%; padding: 9px 14px; }

@media (max-width: 720px) {
  .inner { padding: 0 16px; }
  .feeds { margin-left: 0; }
  .nav-auth { justify-content: flex-start; gap: 8px; }
  .nav-btn { padding: 0 10px; }
  input[type="text"], input[type="password"] { max-width: none; }
  .row-actions .confirm-delete { flex-wrap: wrap; }
}