Tofu/styles/sheet.css
2026-09-18 01:41:02 +02:00

194 lines
6.2 KiB
CSS

/* Tofu — the planning template. A ruled form on paper: a label column on the
left, a writing line on the right. No card, badge, shadow, gradient or motion.
.sheet / .sheet-body get no rule: <article> and <form> are already plain blocks. */
/* --- Sheet -------------------------------------------------------------- */
.sheet-header {
display: flex;
flex-wrap: wrap;
align-items: baseline;
justify-content: space-between;
gap: var(--space-3) var(--space-4);
padding-bottom: var(--space-3);
border-bottom: var(--rule-width) solid var(--rule-strong);
}
.sheet-title {
font-family: var(--font-document);
font-size: var(--size-title);
font-weight: var(--weight-normal);
line-height: var(--line-tight);
color: var(--ink);
}
.sheet-footer {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: var(--space-3);
margin-top: var(--space-4);
padding-top: var(--space-4);
border-top: var(--rule-width) solid var(--rule-strong);
}
/* --- Week navigation ---------------------------------------------------- */
.week-nav { display: flex; gap: var(--space-2); }
/* Chrome and focus come from base.css; only the square footprint is here. */
.week-nav-button {
min-width: var(--control-height); /* 44px square touch target, every viewport */
padding: var(--space-2);
}
/* --- Day ---------------------------------------------------------------- */
.day {
padding-block: var(--space-4);
border-bottom: var(--rule-width) solid var(--rule);
}
.day:last-of-type { border-bottom: 0; }
.day-title {
margin-bottom: var(--space-2);
font-family: var(--font-label);
font-size: var(--size-day);
font-weight: var(--weight-strong);
letter-spacing: var(--tracking-label);
line-height: var(--line-tight);
text-transform: uppercase;
color: var(--ink);
}
/* --- Field -------------------------------------------------------------- */
/* The label is ALWAYS rendered, left of the line, small and quiet. Baseline
alignment sits the label's baseline on the input's writing line. */
.field {
display: grid;
grid-template-columns: var(--label-column) 1fr;
align-items: baseline;
column-gap: var(--space-3);
row-gap: var(--space-1);
margin-block: var(--space-3);
}
.field-label {
justify-self: start;
font-family: var(--font-label);
font-size: var(--size-label);
font-weight: var(--weight-normal);
letter-spacing: var(--tracking-label);
line-height: var(--line-tight);
text-transform: uppercase;
color: var(--ink-label); /* 6.9:1 on paper, 6.0:1 on the focus wash */
}
.field-control {
position: relative; /* anchor for .suggestions */
min-width: 0; /* let a long value shrink instead of widening the grid */
}
.field-input {
display: block;
width: 100%;
min-height: var(--control-height);
padding: var(--field-pad-block) var(--field-pad-inline);
font-family: var(--font-document);
font-size: var(--size-body);
line-height: var(--line-body);
color: var(--ink);
background-color: transparent;
border: 0;
border-bottom: var(--rule-width) solid var(--rule-strong); /* the writing line, 3.0:1 */
border-radius: var(--radius);
-webkit-appearance: none;
appearance: none;
}
.field-input::placeholder { color: var(--ink-label); opacity: 1; }
/* Focus = TWO simultaneous signals: the wash AND a thicker, darker writing line.
The outline goes only because both replacements are visible; padding-bottom
gives back the pixel the border takes, so the text never moves. */
.field-input:focus-visible {
outline: none;
background-color: var(--focus-wash);
border-bottom-color: var(--rule-focus);
border-bottom-width: var(--rule-width-focus);
padding-bottom: calc(var(--field-pad-block) - var(--rule-grow));
}
/* Forced-colors mode paints over our palette, so ask for a real outline there. */
@media (forced-colors: active) {
.field-input:focus-visible,
.button:focus-visible,
.week-nav-button:focus-visible { outline: var(--rule-width-focus) solid; }
}
/* The ingredient line is quieter than the dish (9.4:1, still AAA).
Ambiguity: §4.9 gives no modifier class, so this leans on document order. */
.day .field + .field .field-input { color: var(--ink-soft); }
/* --- Suggestions -------------------------------------------------------- */
/* Not a floating card: a plain box continuing the field's rule, no shadow.
Absolute only so an open list does not push the rest of the week down. */
.suggestions {
position: absolute;
inset-inline: 0;
top: 100%;
z-index: var(--layer-suggestions);
max-height: var(--suggestions-max-height);
overflow-y: auto;
list-style: none;
background-color: var(--paper);
border: var(--rule-width) solid var(--rule-strong);
border-top: 0;
border-radius: var(--radius);
}
.suggestion {
display: flex;
align-items: center;
min-height: var(--control-height);
padding: var(--space-2) var(--field-pad-inline);
border-inline-start: var(--rule-width-focus) solid transparent;
cursor: pointer;
}
.suggestion + .suggestion { border-top: var(--rule-width) solid var(--rule); }
/* The active option is marked by a bar AND a wash — shape, not colour alone. */
.suggestion:hover,
.suggestion[aria-selected="true"] { background-color: var(--focus-wash); }
.suggestion[aria-selected="true"] { border-inline-start-color: var(--rule-focus); }
/* --- Compact viewport --------------------------------------------------- */
/* A media query cannot read a custom property, so 48rem is stated here. One
column, label ABOVE the line — the natural shape of a form on a phone, not a
degraded desktop: same type sizes, same 44px targets. */
@media (width < 48rem) {
#root {
padding: var(--space-5) var(--space-4);
border-inline: 0;
}
/* More air between fields than between a label and its own line: the label
must read as belonging to the line under it, not floating between two. */
.field {
grid-template-columns: 1fr;
align-items: start;
margin-block: var(--space-4);
}
/* Stacked, the label must share the value's left edge, not the rule's. */
.field-label { padding-inline-start: var(--field-pad-inline); }
.sheet-header { flex-direction: column; align-items: stretch; }
.week-nav { justify-content: flex-start; }
}