Files
freno-dev/src/app.css
Michael Freno 9508aee8e1 nook: show the full feature set on the landing page
The landing page sold eight features from around 0.6 and summed up the
rest in one line, while 0.6–0.10 shipped most of the island's modules. It
also said twelve agents (Muse made thirteen) and marked phone approvals
"coming soon" although the iPhone companion is in TestFlight beta.

Adds live demos for terminal replies, the volume mixer, Capture with its
Wings, and floating actions; a bento of module tiles behind a two-way
feature marquee; and an iPhone/Watch beta section whose request form
posts the same email-only report the app's Settings > Phone card sends.
The approval flow, remote sessions and setup, timer, temperatures and
Capture mocks are rebuilt from the app's own views, so the site shows
what the island actually draws.

Tests: report.test.ts pins the landing page's beta request against the
/report schema (red when the schema demands a machine snapshot); nook and
subdomain suites pass.

NOOK-334

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:07:10 -04:00

869 lines
19 KiB
CSS

@import "tailwindcss";
@plugin "@tailwindcss/typography";
/* Register --color-base as animatable color property */
@property --color-base {
syntax: "<color>";
inherits: true;
initial-value: #fbf1c7;
}
:root {
/* Comments indicate what they are used for in vim/term
/* Base Colors (Background/Text) */
--color-base: #fbf1c7; /* Main background color (lightest) */
--color-mantle: #f3eac1; /* Secondary background (slightly darker) */
--color-crust: #e7deb7; /* Lowest background layer (darkest) */
/* Text Colors */
--color-text: #654735; /* Primary text color */
--color-subtext1: #7b5d44; /* Secondary text (comments, less important) */
--color-subtext0: #8f6f56; /* Tertiary text (even less important) */
/* Surface Colors */
--color-surface0: #dfd6b1; /* Surface layer 0 (medium background) */
--color-surface1: #c9c19f; /* Surface layer 1 (darker surface) */
--color-surface2: #a79c86; /* Surface layer 2 (darkest surface) */
/* Overlay Colors */
--color-overlay0: #c9aa8c; /* Overlay layer 0 */
--color-overlay1: #b6977a; /* Overlay layer 1 */
--color-overlay2: #a28368; /* Overlay layer 2 */
/* Accent Colors (Syntax/Highlighting) */
--color-red: #c14a4a; /* Error messages, important keywords */
--color-maroon: #c14a4a; /* Error messages, critical elements */
--color-peach: #c35e0a; /* Warning messages, operators */
--color-yellow: #a96b2c; /* Variables, parameters, attributes */
--color-green: #6c782e; /* Strings, literals, constants */
--color-teal: #4c7a5d; /* Functions, methods, built-ins */
--color-sky: #4c7a5d; /* Functions, methods (alternative) */
--color-sapphire: #4c7a5d; /* Functions, methods (alternative) */
--color-blue: #45707a; /* Keywords, types, classes */
--color-lavender: #45707a; /* Comments, documentation strings */
--color-pink: #945e80; /* Special syntax elements, identifiers */
--color-mauve: #945e80; /* Special syntax elements (alternative) */
--color-flamingo: #c14a4a; /* Error messages, critical elements */
--color-rosewater: #c14a4a; /* Error messages, critical elements */
}
@theme {
--color-rosewater: #c14a4a;
--color-flamingo: #c14a4a;
--color-pink: #945e80;
--color-mauve: #945e80;
--color-red: #c14a4a;
--color-maroon: #c14a4a;
--color-peach: #c35e0a;
--color-yellow: #a96b2c;
--color-green: #6c782e;
--color-teal: #4c7a5d;
--color-sky: #4c7a5d;
--color-sapphire: #4c7a5d;
--color-blue: #45707a;
--color-lavender: #45707a;
--color-text: #654735;
--color-subtext1: #7b5d44;
--color-subtext0: #8f6f56;
--color-overlay2: #a28368;
--color-overlay1: #b6977a;
--color-overlay0: #c9aa8c;
--color-surface2: #a79c86;
--color-surface1: #c9c19f;
--color-surface0: #dfd6b1;
--color-base: #fbf1c7;
--color-mantle: #f3eac1;
--color-crust: #e7deb7;
/* Button text colors: white on dark bg variants (primary/danger),
theme text on light bg variants (secondary). */
--color-button-text: #ffffff;
--color-button-text-alt: var(--color-text);
}
@media (prefers-color-scheme: dark) {
:root {
--color-rosewater: #efc9c2;
--color-flamingo: #ebb2b2;
--color-pink: #f2a7de;
--color-mauve: #b889f4;
--color-red: #ea7183;
--color-maroon: #ea838c;
--color-peach: #f39967;
--color-yellow: #eaca89;
--color-green: #96d382;
--color-teal: #78cec1;
--color-sky: #91d7e3;
--color-sapphire: #68bae0;
--color-blue: #739df2;
--color-lavender: #a0a8f6;
--color-text: #b5c1f1;
--color-subtext1: #a6b0d8;
--color-subtext0: #959ec2;
--color-overlay2: #848cad;
--color-overlay1: #717997;
--color-overlay0: #63677f;
--color-surface2: #505469;
--color-surface1: #3e4255;
--color-surface0: #2c2f40;
--color-base: #1e1e2e;
--color-mantle: #141620;
--color-crust: #0e0f16;
/* Dark mode: dark text on lighter bg variants (primary/danger),
white text on dark bg variants (secondary). */
--color-button-text: var(--color-crust);
--color-button-text-alt: #ffffff;
}
@theme {
--color-rosewater: #efc9c2;
--color-flamingo: #ebb2b2;
--color-pink: #f2a7de;
--color-mauve: #b889f4;
--color-red: #ea7183;
--color-maroon: #ea838c;
--color-peach: #f39967;
--color-yellow: #eaca89;
--color-green: #96d382;
--color-teal: #78cec1;
--color-sky: #91d7e3;
--color-sapphire: #68bae0;
--color-blue: #739df2;
--color-lavender: #a0a8f6;
--color-text: #b5c1f1;
--color-subtext1: #a6b0d8;
--color-subtext0: #959ec2;
--color-overlay2: #848cad;
--color-overlay1: #717997;
--color-overlay0: #63677f;
--color-surface2: #505469;
--color-surface1: #3e4255;
--color-surface0: #2c2f40;
--color-base: #1e1e2e;
--color-mantle: #141620;
--color-crust: #0e0f16;
}
}
/* Manual theme override via classes (higher specificity than media query) */
:root.light {
--color-rosewater: #c14a4a;
--color-flamingo: #c14a4a;
--color-pink: #945e80;
--color-mauve: #945e80;
--color-red: #c14a4a;
--color-maroon: #c14a4a;
--color-peach: #c35e0a;
--color-yellow: #a96b2c;
--color-green: #6c782e;
--color-teal: #4c7a5d;
--color-sky: #4c7a5d;
--color-sapphire: #4c7a5d;
--color-blue: #45707a;
--color-lavender: #45707a;
--color-text: #654735;
--color-subtext1: #7b5d44;
--color-subtext0: #8f6f56;
--color-overlay2: #a28368;
--color-overlay1: #b6977a;
--color-overlay0: #c9aa8c;
--color-surface2: #a79c86;
--color-surface1: #c9c19f;
--color-surface0: #dfd6b1;
--color-base: #fbf1c7;
--color-mantle: #f3eac1;
--color-crust: #e7deb7;
--color-button-text: #ffffff;
--color-button-text-alt: var(--color-text);
}
:root.dark {
--color-rosewater: #efc9c2;
--color-flamingo: #ebb2b2;
--color-pink: #f2a7de;
--color-mauve: #b889f4;
--color-red: #ea7183;
--color-maroon: #ea838c;
--color-peach: #f39967;
--color-yellow: #eaca89;
--color-green: #96d382;
--color-teal: #78cec1;
--color-sky: #91d7e3;
--color-sapphire: #68bae0;
--color-blue: #739df2;
--color-lavender: #a0a8f6;
--color-text: #b5c1f1;
--color-subtext1: #a6b0d8;
--color-subtext0: #959ec2;
--color-overlay2: #848cad;
--color-overlay1: #717997;
--color-overlay0: #63677f;
--color-surface2: #505469;
--color-surface1: #3e4255;
--color-surface0: #2c2f40;
--color-base: #1e1e2e;
--color-mantle: #141620;
--color-crust: #0e0f16;
--color-button-text: var(--color-crust);
--color-button-text-alt: #ffffff;
}
:root {
font-family: "Source Code Pro", monospace;
background-color: var(--color-base);
transition-property: all;
transition-timing-function: var(
--tw-ease,
var(--default-transition-timing-function) /* cubic-bezier(0.4, 0, 0.2, 1) */
);
transition-duration: var(
--tw-duration,
var(--default-transition-duration) /* 150ms */
);
transition-duration: 500ms;
syntax: "*";
inherits: false;
}
::selection {
background-color: var(--color-text);
color: var(--color-crust);
}
::-moz-selection {
background-color: var(--color-text);
color: var(--color-crust);
}
::-webkit-selection {
background-color: var(--color-text);
color: var(--color-crust);
}
#rightbar-content {
scrollbar-width: none;
}
html,
body {
overflow-x: hidden;
}
body {
background: var(--color-base);
color: var(--color-text);
transition: background-color 500ms ease-in-out;
}
[data-typewriter="animated"] [data-char-index] {
opacity: 0;
}
[data-typewriter-ready="true"] [data-char-index] {
transition: opacity 0.05s ease-in;
}
.cursor-typing {
display: inline-block;
width: 2px;
background-color: var(--color-text);
vertical-align: text-bottom;
margin-left: 2px;
position: absolute;
}
/* Block cursor when done typing */
.cursor-block {
display: inline-block;
width: 1ch;
background-color: var(--color-text);
vertical-align: text-bottom;
animation: blink 1s infinite;
margin-left: 2px;
position: absolute;
}
@keyframes blink {
0%,
50% {
opacity: 1;
}
51%,
100% {
opacity: 0;
}
}
.input-group,
.textarea-group {
position: relative;
margin-top: 30px;
}
input.underlinedInput,
textarea.underlinedInput {
font-size: 18px;
padding: 10px 10px 10px 5px;
display: block;
border: none;
}
input.underlinedInput,
textarea.underlinedInput {
border-bottom: 1px solid var(--color-surface2);
}
textarea.underlinedInput {
resize: none;
}
input.underlinedInput:focus,
textarea.underlinedInput:focus {
outline: none;
}
.bar {
position: relative;
display: block;
}
.bar:before,
.bar:after {
content: "";
height: 2px;
width: 0;
bottom: 0px;
position: absolute;
transition: width 0.3s ease-out;
background: var(--color-blue);
}
.bar:before {
left: 50%;
}
.bar:after {
right: 50%;
}
input.underlinedInput:focus ~ .bar:before,
input.underlinedInput:focus ~ .bar:after,
textarea.underlinedInput:focus ~ .bar:before,
textarea.underlinedInput:focus ~ .bar:after {
width: 50%;
}
input.underlinedInput:focus ~ label,
input.underlinedInput:not(:placeholder-shown) ~ label,
textarea.underlinedInput:focus ~ label,
textarea.underlinedInput:not(:placeholder-shown) ~ label {
top: -20px;
font-size: 14px;
color: var(--color-blue);
}
.delete > input.underlinedInput:focus ~ label,
.delete > input.underlinedInput:not(:placeholder-shown) ~ label,
.delete > textarea.underlinedInput:focus ~ label,
.delete > textarea.underlinedInput:not(:placeholder-shown) ~ label {
color: var(--color-red);
}
label.underlinedInputLabel {
font-size: 18px;
font-weight: normal;
position: absolute;
pointer-events: none;
left: 5px;
top: 10px;
transition:
all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1),
color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
}
label.underlinedInputLabel {
color: var(--color-surface1);
}
.vertical-rule-around {
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: flex-end;
}
.vertical-rule-around:before,
.vertical-rule-around:after {
content: "";
flex: 1;
border-left: 1px solid var(--color-surface2);
margin: 10px 0;
}
.checkbox-container {
position: relative;
padding-left: 35px;
cursor: pointer;
font-size: 22px;
user-select: none;
}
.checkbox-container input {
position: absolute;
opacity: 0;
cursor: pointer;
height: 0;
width: 0;
}
.checkmark {
position: absolute;
top: 0;
left: 0;
height: 25px;
width: 25px;
background-color: #eee;
border-radius: 4px;
transition: background-color 0.2s ease;
}
.checkbox-container:hover input ~ .checkmark {
background-color: #ccc;
}
.checkbox-container input:checked ~ .checkmark {
background-color: var(--color-text);
}
input[type="checkbox"] {
-webkit-appearance: none;
appearance: none;
background-color: var(--form-background);
margin: 0;
font: inherit;
width: 1.15em;
height: 1.15em;
border: 2px solid var(--color-blue);
border-radius: 0.15em;
transform: translateY(-0.075em);
display: grid;
place-content: center;
}
input[type="checkbox"]::before {
content: "";
width: 0.65em;
height: 0.65em;
clip-path: circle(25%);
transform: scale(0);
color: var(--color-blue);
transform-origin: center;
transition: 120ms transform ease-in-out;
/* Windows High Contrast Mode */
background-color: CanvasText;
box-shadow: inset 1em 1em var(--color-text);
}
input[type="checkbox"]:checked::before {
transform: scale(1);
}
.shaker:hover {
animation: shaker 0.5s ease;
}
.tooltip {
position: relative;
}
.tooltip .tooltip-text {
visibility: hidden;
min-width: fit-content;
background-color: var(--color-mantle);
color: var(--color-text);
text-align: center;
padding: 5px 0;
border-radius: 6px;
position: absolute;
z-index: 1;
top: 100%;
left: 50%;
opacity: 0;
transition: opacity 0.3s;
}
.tooltip:hover .tooltip-text {
visibility: visible;
opacity: 1;
}
.rule-around {
display: flex;
align-items: center;
}
.rule-around:before,
.rule-around:after {
content: "";
flex: 1;
border-top: 1px solid var(--color-text);
margin: 0 10px;
}
.hover-underline-animation {
display: inline-block;
position: relative;
}
.hover-underline-animation::after {
content: "";
position: absolute;
width: 100%;
transform: scaleX(0);
height: 1px;
bottom: 0;
left: 0;
transform-origin: bottom right;
transition: transform 0.35s ease-out;
}
.hover-underline-animation:hover::after {
transform: scaleX(1);
transform-origin: bottom left;
background-color: var(--color-surface2);
}
a.hover-underline-animation:hover::after {
background-color: var(--color-blue) !important;
}
.image-overlay::before {
content: "";
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: rgba(255, 255, 255, 0.1);
}
.iframe-wrapper {
position: relative;
min-height: 300px;
width: 100%;
min-width: min(350px, 100%);
max-width: 100% !important;
background-color: black;
}
.iframe-wrapper iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
/* Hamburger menu button - only show on non-touch devices under mobile breakpoint */
@media (max-width: 767px) {
.hamburger-menu-btn {
display: block !important;
}
}
@keyframes shaker {
0% {
transform: rotate(0deg);
}
33% {
transform: rotate(15deg);
}
66% {
transform: rotate(-15deg);
}
100% {
transform: rotate(0deg);
}
}
.shaker:hover {
animation: shaker 0.5s ease;
}
/* ── Nook landing: campfire sprite frame cycling ──────────────────── */
/* Each frame layer is stacked; its animation holds opacity 1 during its
slot and 0 otherwise, so the stack reads as stop-motion at the app's
sprite cadence (frame time = total duration / frame count). The phase
lives inside the keyframes and every layer animates delay-free from the
same clock WebKit quantizes animation starts per cycle wrap, so
delay-offset layers can drop a wrap frame and show BOTH transparent
for a frame (the "blank frame" flicker). Complementary holds mean any
moment sums to exactly one opaque layer. Linear timing: steps()
holds mis-swap at boundary frames under WebKit. */
/* 2-frame sprites: layer i uses campfire-slot-2-{a,b}. */
@keyframes campfire-slot-2-a {
0%,
49.99% {
opacity: 1;
}
50%,
100% {
opacity: 0;
}
}
@keyframes campfire-slot-2-b {
0%,
49.99% {
opacity: 0;
}
50%,
100% {
opacity: 1;
}
}
/* 6-frame sprites (question): six slot phases. */
@keyframes campfire-slot-6-a {
0%,
16.66% {
opacity: 1;
}
16.67%,
100% {
opacity: 0;
}
}
@keyframes campfire-slot-6-b {
0%,
16.66% {
opacity: 0;
}
16.67%,
33.32% {
opacity: 1;
}
33.33%,
100% {
opacity: 0;
}
}
@keyframes campfire-slot-6-c {
0%,
33.32% {
opacity: 0;
}
33.33%,
49.99% {
opacity: 1;
}
50%,
100% {
opacity: 0;
}
}
@keyframes campfire-slot-6-d {
0%,
49.99% {
opacity: 0;
}
50%,
66.66% {
opacity: 1;
}
66.67%,
100% {
opacity: 0;
}
}
@keyframes campfire-slot-6-e {
0%,
66.66% {
opacity: 0;
}
66.67%,
83.32% {
opacity: 1;
}
83.33%,
100% {
opacity: 0;
}
}
@keyframes campfire-slot-6-f {
0%,
83.32% {
opacity: 0;
}
83.33%,
100% {
opacity: 1;
}
}
.campfire-frame {
animation: var(--slot-kf, campfire-slot-2-a) var(--campfire-duration, 1s)
linear infinite;
opacity: 0;
}
/* ── Nook landing: breakdown section reveal ───────────────────────── */
.reveal {
opacity: 0;
transform: translateY(24px);
transition:
opacity 0.7s cubic-bezier(0.22, 1, 0.36, 1),
transform 0.7s cubic-bezier(0.22, 1, 0.36, 1);
}
.reveal.in {
opacity: 1;
transform: translateY(0);
}
@media (prefers-reduced-motion: reduce) {
.reveal {
opacity: 1;
transform: none;
transition: none;
}
.campfire-frame {
animation: none;
opacity: 1 !important;
}
.campfire-frame + .campfire-frame {
display: none;
}
}
/* ── Nook landing: fan card shake, builds with fan speed ───────── */
.fan-card-shake {
transform-origin: left center;
animation-name: fan-shake;
animation-duration: var(--fan-speed, 0.4s);
animation-iteration-count: infinite;
animation-timing-function: ease-in-out;
}
@keyframes fan-shake {
0%, 100% { transform: rotate(0deg); }
50% { transform: rotate(calc(var(--fan-amp, 0deg) * -1)); }
}
/* Sputtering sparks off the fan card at max speed */
.fan-spark {
position: absolute;
border-radius: 9999px;
background: #ffd23f;
box-shadow: 0 0 4px 1px rgba(255, 140, 0, 0.9);
animation: fan-spark-fly var(--sd, 0.5s) ease-out var(--sdel, 0s) infinite;
}
@keyframes fan-spark-fly {
0% { transform: translate(0, 0) scale(1); opacity: 0; }
10% { opacity: 1; }
100% { transform: translate(var(--sx, 20px), var(--sy, 30px)) scale(0.4); opacity: 0; }
}
/* Demo pulse: battery bolt */
@keyframes nook-pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.35; }
}
/* ── Nook landing: newer demos ─────────────────────────────────── */
.nook-caret {
animation: nook-pulse 0.8s steps(2, jump-none) infinite;
}
/* Live signal inside a mixer fader */
.nook-meter {
transform-origin: left center;
animation: nook-meter 0.6s ease-in-out infinite alternate;
}
@keyframes nook-meter {
0% { transform: scaleX(0.35); }
40% { transform: scaleX(0.8); }
100% { transform: scaleX(0.55); }
}
/* Waveform slot bars */
.nook-wave {
height: 100%;
transform-origin: center;
animation: nook-wave 0.8s ease-in-out infinite alternate;
}
@keyframes nook-wave {
0% { transform: scaleY(0.25); }
100% { transform: scaleY(1); }
}
/* Now Playing progress and artwork sheen */
.nook-progress {
animation: nook-progress 14s linear infinite;
}
@keyframes nook-progress {
from { width: 8%; }
to { width: 100%; }
}
.nook-art {
animation: nook-art 9s linear infinite;
}
@keyframes nook-art {
to { filter: hue-rotate(360deg); }
}
/* A ringing timer's tape breathes between 35 % and full */
.nook-tape-ring {
animation: nook-tape-ring 0.9s ease-in-out infinite alternate;
}
@keyframes nook-tape-ring {
from { opacity: 0.35; }
to { opacity: 1; }
}
/* Keep Awake coffee steam */
.nook-steam path {
animation: nook-steam 1.8s ease-in-out infinite;
}
@keyframes nook-steam {
0% { transform: translateY(2px); opacity: 0; }
40% { opacity: 1; }
100% { transform: translateY(-4px); opacity: 0; }
}
/* Two-way feature marquee; edges fade, hover pauses */
.nook-marquee {
overflow: hidden;
mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}
.nook-marquee-track {
display: flex;
gap: 0.625rem;
width: max-content;
padding-right: 0.625rem;
animation: nook-marquee 60s linear infinite;
}
.nook-marquee-track.reverse {
animation-direction: reverse;
}
.nook-marquee:hover .nook-marquee-track {
animation-play-state: paused;
}
@keyframes nook-marquee {
to { transform: translateX(-50%); }
}
@media (prefers-reduced-motion: reduce) {
.nook-meter,
.nook-wave,
.nook-progress,
.nook-art,
.nook-tape-ring,
.nook-steam path,
.nook-caret {
animation: none;
}
.nook-marquee {
mask-image: none;
}
.nook-marquee-track {
animation: none;
flex-wrap: wrap;
justify-content: center;
width: auto;
padding: 0 1rem;
}
.nook-marquee-track > [aria-hidden="true"] {
display: none;
}
}