Portfolio/app/assets/css/main.css
2026-09-08 19:54:06 +02:00

497 lines
9.7 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.

/* ---------------------------------------------------------------------------
nak0x.dev — a terminal-shaped sheet of paper.
Everything is monospace, everything sits on a 1px ink rule.
--------------------------------------------------------------------------- */
:root,
:root[data-theme='light'] {
--paper: #f6f3ec;
--paper-soft: #efece4;
--ink: #0a0a0a;
--ink-mute: #4a4a4a;
--ink-faint: #8a8a8a;
--rule: #0a0a0a;
--accent: #D97629;
}
:root[data-theme='dark'] {
--paper: #0e0e10;
--paper-soft: #16161a;
--ink: #ece9e0;
--ink-mute: #9a9a9a;
--ink-faint: #6a6a6a;
--rule: #ece9e0;
--accent: #D97629;
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme]) {
--paper: #0e0e10;
--paper-soft: #16161a;
--ink: #ece9e0;
--ink-mute: #9a9a9a;
--ink-faint: #6a6a6a;
--rule: #ece9e0;
--accent: #e56fd8;
}
}
* {
box-sizing: border-box;
}
html,
body {
margin: 0;
padding: 0;
background: var(--paper);
color: var(--ink);
}
html {
font-family: 'JetBrains Mono', ui-monospace, 'IBM Plex Mono', Menlo, Monaco,
'Liberation Mono', 'DejaVu Sans Mono', Consolas, monospace;
font-size: 15px;
line-height: 1.55;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
min-height: 100vh;
}
::selection {
background: var(--ink);
color: var(--paper);
}
h1,
h2,
h3,
h4 {
font-weight: 600;
margin: 0;
letter-spacing: -0.01em;
}
h1 { font-size: 1.5rem; }
h2 { font-size: 1.05rem; text-transform: lowercase; }
h3 { font-size: 0.95rem; text-transform: lowercase; color: var(--ink-mute); }
p { margin: 0; }
a {
color: var(--ink);
text-decoration: underline;
text-decoration-thickness: 1px;
text-underline-offset: 3px;
}
a:hover {
background: var(--accent);
color: var(--paper);
text-decoration: none;
}
hr {
border: 0;
border-top: 1px solid var(--rule);
margin: 1rem 0;
}
pre { margin: 0; font-family: inherit; }
/* --- layout --------------------------------------------------------------- */
.paper {
max-width: 1080px;
margin: 0 auto;
padding: 2.5rem 1.25rem 6rem;
position: relative;
z-index: 1;
}
.stack > * + * { margin-top: 1.25rem; }
.stack-sm > * + * { margin-top: 0.5rem; }
.row {
display: flex;
gap: 1rem;
align-items: baseline;
flex-wrap: wrap;
}
.column { display: grid; gap: 1.25rem; }
.grid-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1.25rem;
}
@media (max-width: 700px) {
.grid-2 { grid-template-columns: 1fr; }
}
/* --- the frame: a 1px box with `+` corners, like a TUI widget -------------- */
.frame {
position: relative;
border: 1px solid var(--ink);
background: var(--paper);
padding: 1rem 1.25rem;
}
.frame::before,
.frame::after {
content: '+';
position: absolute;
width: 12px;
height: 12px;
display: flex;
align-items: center;
justify-content: center;
line-height: 1;
background: var(--paper);
color: var(--ink);
font-size: 0.95rem;
z-index: 2;
pointer-events: none;
}
.frame::before { top: -6px; left: -6px; }
.frame::after { bottom: -6px; right: -6px; }
.frame-tag {
position: absolute;
top: -0.6rem;
left: 0.75rem;
background: var(--paper);
padding: 0 0.4rem;
font-size: 0.75rem;
text-transform: lowercase;
letter-spacing: 0.04em;
color: var(--ink-mute);
z-index: 2;
}
.accent-on-hover:hover {
/* the box is often an <a>, so undo the global link-hover fill */
background: var(--paper);
border-color: var(--accent);
}
.accent-on-hover:hover::before,
.accent-on-hover:hover::after {
color: var(--accent);
}
/* --- text helpers --------------------------------------------------------- */
.muted { color: var(--ink-mute); }
.faint { color: var(--ink-faint); }
.small { font-size: 0.85rem; }
.tiny { font-size: 0.75rem; letter-spacing: 0.04em; text-transform: uppercase; }
.accent { color: var(--accent); }
.lower { text-transform: lowercase; }
.nowrap { white-space: nowrap; }
.ascii-rule {
color: var(--ink-faint);
white-space: pre;
overflow: hidden;
user-select: none;
line-height: 1;
}
/* --- lists ---------------------------------------------------------------- */
.bullets {
list-style: none;
padding: 0;
margin: 0.85rem 0 0;
color: var(--ink-mute);
font-size: 0.92rem;
}
.bullets li {
padding-left: 1.25rem;
position: relative;
}
.bullets li::before {
content: '\203A';
position: absolute;
left: 0;
color: var(--accent);
}
.def-list {
margin: 0;
display: grid;
grid-template-columns: max-content 1fr;
column-gap: 1rem;
row-gap: 0.35rem;
font-size: 0.9rem;
line-height: 1.5;
}
.def-list dt {
color: var(--ink-mute);
text-transform: lowercase;
letter-spacing: 0.04em;
white-space: nowrap;
}
.def-list dd {
margin: 0;
min-width: 0;
position: relative;
padding-left: 1rem;
}
.def-list dd::before {
content: '\203A';
position: absolute;
left: 0;
color: var(--accent);
}
@media (max-width: 480px) {
.def-list { grid-template-columns: 1fr; row-gap: 0.15rem; }
.def-list dt { margin-top: 0.4rem; }
.def-list dt:first-of-type { margin-top: 0; }
}
/* --- rendered markdown ---------------------------------------------------- */
.prose { font-size: 0.95rem; }
.prose > * + * { margin-top: 1rem; }
.prose h2,
.prose h3,
.prose h4 {
margin-top: 2rem;
text-transform: none;
color: var(--ink);
scroll-margin-top: 2rem;
}
.prose h2 { font-size: 1.15rem; }
.prose h2::before,
.prose h3::before {
content: '# ';
color: var(--accent);
}
.prose h3 { font-size: 1rem; }
.prose ul,
.prose ol { padding-left: 1.5rem; margin-top: 0.75rem; }
.prose li + li { margin-top: 0.25rem; }
.prose li::marker { color: var(--accent); }
.prose img {
display: block;
max-width: 100%;
height: auto;
border: 1px solid var(--ink);
}
.prose blockquote {
margin: 0;
border-left: 2px solid var(--accent);
padding-left: 1rem;
color: var(--ink-mute);
}
.prose code {
background: var(--paper-soft);
border: 1px solid var(--paper-soft);
padding: 0.05em 0.35em;
font-size: 0.9em;
}
.prose pre {
border: 1px solid var(--ink);
padding: 1rem;
overflow-x: auto;
background: var(--paper-soft) !important;
font-size: 0.85rem;
line-height: 1.5;
}
.prose pre code {
background: none;
border: 0;
padding: 0;
font-size: inherit;
}
.prose table {
width: 100%;
border-collapse: collapse;
font-size: 0.88rem;
display: block;
overflow-x: auto;
}
.prose th,
.prose td {
border: 1px solid var(--ink-faint);
padding: 0.35rem 0.6rem;
text-align: left;
}
.prose .heading-anchor {
text-decoration: none;
color: var(--ink-faint);
opacity: 0;
padding-left: 0.4rem;
}
.prose :is(h2, h3, h4):hover .heading-anchor { opacity: 1; }
/* shiki emits both themes; swap them with the css variable trick */
html[data-theme='dark'] .shiki,
html[data-theme='dark'] .shiki span {
color: var(--shiki-dark) !important;
background-color: transparent !important;
font-style: var(--shiki-dark-font-style) !important;
}
@media (prefers-color-scheme: dark) {
html:not([data-theme]) .shiki,
html:not([data-theme]) .shiki span {
color: var(--shiki-dark) !important;
background-color: transparent !important;
}
}
/* --- forms & buttons ------------------------------------------------------
Used by /dash. Same rules as the rest of the page: 1px ink, no radius,
monospace, inversion on interaction.
--------------------------------------------------------------------------- */
.field {
display: block;
}
.field > .label {
display: block;
font-size: 0.75rem;
letter-spacing: 0.04em;
text-transform: lowercase;
color: var(--ink-mute);
margin-bottom: 0.25rem;
}
.input,
.textarea,
.select {
width: 100%;
font: inherit;
font-size: 0.9rem;
color: var(--ink);
background: var(--paper-soft);
border: 1px solid var(--ink-faint);
padding: 0.35rem 0.5rem;
border-radius: 0;
-webkit-appearance: none;
appearance: none;
}
.input:focus,
.textarea:focus,
.select:focus {
outline: none;
border-color: var(--accent);
box-shadow: inset 0 0 0 1px var(--accent);
}
.textarea {
resize: vertical;
line-height: 1.55;
tab-size: 2;
}
.select {
/* room for the caret we draw with the wrapper */
padding-right: 1.5rem;
}
.checkbox {
display: inline-flex;
align-items: center;
gap: 0.45rem;
font-size: 0.85rem;
cursor: pointer;
user-select: none;
}
.checkbox input {
appearance: none;
width: 1rem;
height: 1rem;
border: 1px solid var(--ink-faint);
background: var(--paper-soft);
display: grid;
place-content: center;
cursor: pointer;
margin: 0;
}
.checkbox input:checked {
border-color: var(--accent);
}
.checkbox input:checked::after {
content: '×';
color: var(--accent);
font-size: 0.9rem;
line-height: 1;
}
.btn {
font: inherit;
font-size: 0.85rem;
color: var(--ink);
background: var(--paper);
border: 1px solid var(--ink);
padding: 0.3rem 0.75rem;
cursor: pointer;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 0.4rem;
white-space: nowrap;
border-radius: 0;
}
.btn:hover:not(:disabled) {
background: var(--ink);
color: var(--paper);
}
.btn:disabled {
opacity: 0.45;
cursor: not-allowed;
}
.btn-primary {
border-color: var(--accent);
color: var(--accent);
}
.btn-primary:hover:not(:disabled) {
background: var(--accent);
color: var(--paper);
}
.btn-danger:hover:not(:disabled) {
background: #c0392b;
border-color: #c0392b;
color: #fff;
}
.btn-ghost {
border-color: transparent;
padding-inline: 0.35rem;
}
.btn-ghost:hover:not(:disabled) {
background: var(--ink);
color: var(--paper);
}
/* inline status line: ok / working / failed */
.flash {
font-size: 0.8rem;
padding: 0.35rem 0.6rem;
border: 1px solid var(--ink-faint);
}
.flash-ok {
border-color: var(--accent);
color: var(--accent);
}
.flash-error {
border-color: #c0392b;
color: #c0392b;
}