h1.giga { font-size: var(--pt-double-canon); } h1.mega { font-size: var(--pt-canon); } h1, header > p { font-size: var(--pt-double-great-primer); } h2 { font-size: var(--pt-double-english); } h3 { font-size: var(--pt-double-pica); } h4 { font-size: var(--pt-paragon); } h5 { font-size: var(--pt-great-primer); } h6 { font-size: var(--pt-english); } small { font-size: var(--pt-small-pica); } small.micro { font-size: var(--pt-long-primer); } small.nano { font-size: var(--pt-bourgeois); } small.pico { font-size: var(--pt-minion); } html { width: 100%; height: 100%; } /* The aberration is a discrete jitter, so it is stepped rather than interpolated: step-end repaints 21 times per cycle instead of once per frame. text-shadow is not compositable, and this one inherits to every glyph on the page, so a smooth tween would repaint the whole document at refresh rate. Stepping also reads more like chroma noise than a ramp does. */ body { animation: crt-aberration 1.6s step-end infinite; /* Anchors the vignette to the viewport. Without it the gradient is sized to body's box, which is document-height, so on a long page its centre sits at the middle of the document and the bloom is off-screen at scroll-top. */ background-attachment: fixed; background-color: var(--color-bg); background-image: radial-gradient( var(--crt-vignette-core), var(--crt-vignette-edge) 120% ); color: var(--color-fg); column-gap: var(--gutter-large); display: grid; font-size: var(--pt-pica); grid-template-rows: auto 1fr auto; left: 0; overflow-x: hidden; padding-inline: var(--page-padding-inline); place-items: center; position: absolute; right: 0; row-gap: var(--stack-large); /* Single source of truth for the glow: when the animation is off, --crt-shift is 0 and this is exactly the static glow. Previously the animation overrode a separate `0 0 5px var(--color-glow)` rule, so reduce-motion users saw a 5px white glow and everyone else a 3px currentColor one — two looks nobody had chosen. */ text-shadow: calc(var(--crt-shift, 0) * var(--crt-aberration-max)) 0 1px var(--crt-shadow-cool), calc(var(--crt-shift, 0) * var(--crt-aberration-max) * -1) 0 1px var(--crt-shadow-warm), 0 0 var(--crt-glow-blur) var(--color-glow); top: 0; } /* Fixed rather than absolute: these used to be document-sized layers that relied on background-attachment to keep the pattern still. Pinning them to the viewport gets the same effect while bounding the painted layer, and it covers the screen regardless of how tall the content is. */ body::before, body::after { content: ""; inset: 0; pointer-events: none; position: fixed; z-index: 2; } /* Scanlines and aperture grille. repeating-linear-gradient with hard stops replaces background-size tiling: the grille used to be a smooth R->G->B ramp across 3px, where a real grille is three discrete stripes. */ body::before { background-image: repeating-linear-gradient( transparent 0 calc(var(--crt-scanline-pitch) / 2), var(--crt-scanline-color) 0 var(--crt-scanline-pitch) ), repeating-linear-gradient( 90deg, var(--crt-grille-r) 0 calc(var(--crt-grille-pitch) / 3), var(--crt-grille-g) 0 calc(var(--crt-grille-pitch) / 3 * 2), var(--crt-grille-b) 0 var(--crt-grille-pitch) ); } /* Phosphor flicker. The old version tinted with rgba(18, 16, 16, 0.1) at 0.08-0.96 opacity: an effective alpha of 0.008-0.096 of a colour ~7% lighter than the background, i.e. a peak lift of under 2/255. It was invisible. Tinting with the accent at ~3% is small enough to cost no measurable contrast and large enough to actually see. */ body::after { animation: crt-flicker 3s step-end infinite; background: var(--color-accent); opacity: var(--crt-flicker-base); } :root[data-reduce-motion="on"] body, :root[data-reduce-motion="on"] body::after { animation: none; } @media (prefers-reduced-motion: reduce) { :root:not([data-reduce-motion="off"]) body, :root:not([data-reduce-motion="off"]) body::after { animation: none; } } :root[data-more-contrast="on"] body { background-image: none; text-shadow: none; animation: none; } :root[data-more-contrast="on"] body::before, :root[data-more-contrast="on"] body::after { content: none; } @media (prefers-contrast: more) { :root:not([data-more-contrast="off"]) body { background-image: none; text-shadow: none; animation: none; } :root:not([data-more-contrast="off"]) body::before, :root:not([data-more-contrast="off"]) body::after { content: none; display: none; background: none; animation: none; } } @keyframes crt-flicker { 0% { opacity: 0.03; } 7% { opacity: 0.024; } 13% { opacity: 0.041; } 19% { opacity: 0.028; } 26% { opacity: 0.019; } 31% { opacity: 0.036; } 38% { opacity: 0.026; } 44% { opacity: 0.048; } 51% { opacity: 0.022; } 58% { opacity: 0.033; } 63% { opacity: 0.017; } 70% { opacity: 0.039; } 77% { opacity: 0.027; } 83% { opacity: 0.044; } 91% { opacity: 0.021; } 100% { opacity: 0.03; } } /* Normalised from the original per-keyframe pixel offsets, divided by their max (3.8969px), so the rhythm is unchanged but the amplitude is now set in one place by --crt-aberration-max. */ @keyframes crt-aberration { 0% { --crt-shift: 0.113; } 5% { --crt-shift: 0.717; } 10% { --crt-shift: 0.008; } 15% { --crt-shift: 0.103; } 20% { --crt-shift: 0.893; } 25% { --crt-shift: 0.414; } 30% { --crt-shift: 0.18; } 35% { --crt-shift: 1; } 40% { --crt-shift: 0.993; } 45% { --crt-shift: 0.573; } 50% { --crt-shift: 0.021; } 55% { --crt-shift: 0.61; } 60% { --crt-shift: 0.565; } 65% { --crt-shift: 0.735; } 70% { --crt-shift: 0.125; } 75% { --crt-shift: 0.486; } 80% { --crt-shift: 0.021; } 85% { --crt-shift: 0.025; } 90% { --crt-shift: 0.884; } 95% { --crt-shift: 0.56; } 100% { --crt-shift: 0.673; } } header, main, footer { width: 100%; } header, footer { padding-block: var(--stack-large); text-align: center; a { text-decoration: none; } } nav { padding-block: var(--stack-small); padding-inline: var(--gutter-small); width: 100%; } .display-prefs-nav { position: absolute; top: 0; right: 0; z-index: 3; } .display-prefs-nav summary { align-items: center; cursor: pointer; display: flex; justify-content: flex-end; list-style: none; padding-block: var(--stack-small); padding-inline: 0; } .display-prefs-nav summary::-webkit-details-marker { display: none; } .display-prefs-nav summary::before { color: transparent; text-shadow: none; content: "[" / ""; } .display-prefs-nav summary::after { color: transparent; text-shadow: none; content: "]" / ""; } .display-prefs-nav summary:hover::before, .display-prefs-nav summary:hover::after { color: var(--color-glow); text-shadow: inherit; } .display-prefs-nav summary > span { translate: 0 -0.225em; } .display-prefs-panel { background-color: var(--color-bg); border: 1px solid var(--color-accent); min-inline-size: 10em; padding-block: var(--stack-small); padding-inline: var(--gutter-small); position: absolute; right: 0; inline-size: max-content; } nav ul { column-gap: var(--gutter-large); display: flex; flex-wrap: wrap; justify-content: center; list-style: none; margin-block-start: 0; row-gap: var(--stack-large); } nav li { align-content: center; white-space: nowrap; } .iconBox { display: inline-block; height: 19px; width: 19px; } p, ul { margin-block-end: var(--stack-large); max-inline-size: var(--line-length); } form p { margin-block-end: none; } a { color: var(--color-fg); margin: 0 -1ch; text-decoration: underline; text-decoration-color: var(--color-glow); /* Brackets always occupy their space and hover only reveals them. Swapping ch-based padding for a rendered glyph shifts the text by a subpixel, because the fluid root font-size makes 1ch fractional. The / "" keeps them out of the accessible name. */ &::before { color: transparent; text-shadow: none; content: "[" / ""; } &::after { color: transparent; text-shadow: none; content: "]" / ""; } &:hover::before, &:hover::after { color: var(--color-glow); text-shadow: inherit; } &:hover { color: var(--color-fg); text-decoration: none; } } footer a { text-decoration: none; width: fit-content; display: inline-block; margin: 0 auto; padding: 0; &::before, &::after { content: none; } } ul { list-style-type: square; } fieldset { border: 2px solid var(--color-fg); padding-block: var(--stack-small); padding-inline: var(--gutter-small); } label { display: block; } label:has(> input[type="checkbox"]), label:has(> input[type="radio"]) { align-items: center; column-gap: var(--gutter-large); display: flex; margin-block: var(--stack-large); } /* TODO: use textarea-wrapper strategy to implement invisible inputs idea, but have the text-glow effect done on the ::after pseudo-element and hide the input when it isnt focused, and swap when focused */ input[type="text"], input[type="email"], input[type="password"], input[type="search"], input[type="url"], input[type="tel"], input[type="number"], input[type="date"], input[type="time"], input[type="datetime-local"], textarea, .textarea-wrapper::after, select { background-color: var(--color-bg); border: 2px solid var(--color-accent); padding-block: var(--stack-small); padding-inline: var(--gutter-small); transition: border-color 0.5s, box-shadow 0.5s; width: 100%; } textarea, .textarea-wrapper::after { grid-area: 1 / 1 / 2 / 2; /* Place on top of each other */ min-height: calc(2rlh + (var(--stack-small) * 2) - 2px); } .textarea-wrapper { display: grid; } .textarea-wrapper::after { content: attr(data-replicated-value) " "; /* Needed to prevent jumpy behavior */ visibility: hidden; white-space: pre-wrap; } .textarea-wrapper > textarea { overflow: hidden; resize: none; } input:focus, textarea:focus, select:focus { box-shadow: 0 0 5px var(--color-glow); outline: none; } input:disabled, textarea:disabled, select:disabled, button:disabled, fieldset:disabled { cursor: not-allowed; opacity: 0.5; } button, input[type="submit"], input[type="reset"] { background-color: var(--color-accent); box-shadow: 0 0 2px var(--color-glow); color: var(--color-bg); margin-block: var(--stack-small); padding-block: var(--stack-small); padding-inline: var(--gutter-small); transition: background-color 0.5s; text-align: center; } button:hover:enabled, input[type="submit"]:hover:enabled, input[type="reset"]:hover:enabled { background-color: var(--color-accent-bright); } button:active:enabled, input[type="submit"]:active:enabled, input[type="reset"]:active:enabled { background-color: var(--color-accent-bright); } svg { fill: currentColor; } /*input[type="checkbox"], input[type="radio"] { appearance: none; background-color: var(--color-bg); border: 2px solid var(--color-fg); cursor: pointer; height: var(--pt-english); position: relative; vertical-align: middle; width: var(--pt-english); } input[type="checkbox"] { border-radius: 3px; } input[type="radio"] { border-radius: 50%; } input[type="checkbox"]:checked, input[type="radio"]:checked { background-color: var(--color-accent); } input[type="checkbox"]:checked::after { color: var(--color-accent); content: "✓"; font-size: var(--pt-long-primer); left: 2px; position: absolute; top: -1px; } input[type="radio"]:checked::after { background-color: var(--color-accent); border-radius: 50%; content: ""; height: var(--pt-minion); left: 3px; position: absolute; top: 3px; width: var(--pt-minion); } select { appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%23333' viewBox='0 0 16 16'%3E%3Cpath d='M7.247 11.14L2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z'/%3E%3C/svg%3E"); background-position: calc(100% - 8px) center; background-repeat: no-repeat; padding-block-end: var(--stack-small); } input[type="file"] { background-color: var(--color-bg); border: 2px dashed var(--color-fg); cursor: pointer; padding: 20px; text-align: center; } input[type="file"]::file-selector-button { background-color: var(--color-accent); border: none; border-radius: 4px; color: var(--bg-color); cursor: pointer; font-family: inherit; margin-right: 10px; padding: 8px 16px; } input[type="range"] { appearance: none; background-color: var(--color-fg); border-radius: 3px; height: 6px; margin: 10px 0; width: 100%; } input[type="range"]::-webkit-slider-thumb { appearance: none; background-color: var(--color-accent); border: 2px solid var(--bg-color); border-radius: 50%; cursor: pointer; height: 18px; width: 18px; } input[type="color"] { border: 2px solid var(--color-fg); border-radius: 4px; cursor: pointer; height: 40px; padding: 2px; width: 60px; } input:invalid, textarea:invalid, select:invalid { border-color: var(--color-accent); } label.required::after { color: var(--color-accent); content: "*"; margin-left: 4px; } button, input[type="submit"], input[type="reset"] { background-color: var(--color-accent); border: 2px solid var(--color-accent); border-radius: 4px; color: var(--bg-color); cursor: pointer; font-family: inherit; font-size: 1rem; padding: 10px 20px; transition: all 0.2s; } button:hover, input[type="submit"]:hover, input[type="reset"]:hover { background-color: var(--color-fg); border-color: var(--color-fg); transform: translateY(-1px); } button:active, input[type="submit"]:active, input[type="reset"]:active { transform: translateY(1px); } progress { background-color: var(--color-bg); border: 2px solid var(--color-fg); border-radius: 4px; height: 20px; width: 100%; } progress::-webkit-progress-bar { background-color: var(--color-bg); border-radius: 2px; } progress::-webkit-progress-value { background-color: var(--color-accent); border-radius: 2px; } */