/* ============================================================
   Screens.

   The content area sits below the two chrome bars and above the
   dock. Coordinates inside a screen are relative to that area,
   which begins 109.2px down the mockup's frame.
   ============================================================ */

#screen {
	position: absolute;
	left: 0;
	top: calc(var(--status-h) + var(--header-h));
	bottom: var(--dock-h);
	width: var(--frame-w);
	overflow: hidden;
	background: var(--ground);
}
/* Signed out there is no app header and no dock, so the content area runs
   from under the status bar to the foot of the frame. */
#frame[data-signed="false"] #screen { top: var(--status-h); bottom: 0; }

/* The splash and Oxegen's own login are the whole frame — the status bar is
   Oxegen saying it is present, and on these two it is not yet. */
#frame[data-bare="true"] #screen { top: 0; bottom: 0; }

/* Every screen fills the content area; only its contents are placed. It is
   drawn a lift taller than the area and pulled up by that much, so measured
   coordinates rise clear of the browser's bottom toolbar while the screen's
   own background still reaches the foot of the frame. Scrolling regions add
   the lift back to their top, so they stay where they were. */
.screen { position: relative; width: 100%; height: 100%; }

/* A screen drawn at fixed coordinates scrolls, because the mockup's frame is
   taller than a real phone once the browser has taken its share. The canvas is
   pulled up by the lift, so the content starts at a consistent top margin
   rather than the mockup's larger one, and is sized in app.js to what it holds
   plus a slightly larger margin beneath. */
.screen.scrolls {
	position: absolute;
	inset: 0;
	overflow-y: auto;
	overflow-x: hidden;
	scrollbar-width: none;
	-webkit-overflow-scrolling: touch;
}
.screen.scrolls::-webkit-scrollbar { display: none; }
/* margin-top is set per screen in app.js, clamped so the content never rises
   above the top margin. */
.canvas { position: relative; width: 100%; }

/* ── Splash ────────────────────────────────────────────────── */

/* Full-screen cyan. Nothing is drawn over it: this is the moment before Oxegen
   is ready, so it has no status bar and no chrome of any kind. */
.screen-splash { background: var(--accent-alt); }
.screen-splash .art { width: 100%; height: 100%; }
.loader { position: absolute; }
.wordmark { position: absolute; color: var(--ink-inverse); }

/* The ring turns as one, so the circles keep their order and their sizes read
   as a single direction of travel. */
.waiter {
	position: absolute; inset: 0;
	animation: waiter-turn var(--motion-spin) linear infinite;
}
.waiter-dot {
	position: absolute;
	border-radius: var(--radius-pill);
	background: var(--accent);
}
@keyframes waiter-turn { to { transform: rotate(360deg); } }

/* ── Login ─────────────────────────────────────────────────── */

/* Oxegen's own login is the whole screen, not a card inset in someone else's. */
.screen-login { background: var(--accent-alt); color: var(--ink-inverse); }
.screen-login .art { width: 100%; height: 100%; }

.pp-label { font-size: var(--size-display); line-height: 1; }
.pp-wrap {
	position: absolute;
	background: var(--ground);
	border-radius: var(--radius-field);
	display: flex; align-items: center;
}
.pp-input {
	flex: 1; min-width: 0;
	border: none; outline: none;
	appearance: none;
	background: var(--ground);
	font-family: var(--font-body); font-size: 20px;
	color: var(--accent);
	padding: 0 7px;
	letter-spacing: 2px;
}
.pp-eye { color: var(--accent); padding-right: 14px; flex: none; }

.pp-login, .pp-join {
	background: var(--accent); color: var(--ink-inverse);
	border-radius: var(--radius-soft);
	display: flex; align-items: center; justify-content: center;
	font-size: var(--size-display);
}
.pp-forgot { color: var(--ink-inverse); font-size: 14px; line-height: 1; }

/* ── The passphrase the app makes ──────────────────────────── */

/* The login screen carried on, so it is the login screen's ground, column and
   buttons. Only the plate the words are shown on is new.

   The column flows rather than placing each part absolutely, because the one
   thing on it whose size is not known in advance is the phrase: eight words of
   three to seven letters wrap to two rows or to three, and everything under
   them has to move with that. */
.screen-passgen { background: var(--accent-alt); color: var(--ink-inverse); }
.pass-back { color: var(--ink-inverse); }
.pass-head { font-size: var(--size-display); line-height: 1; }
.pass-caption { position: absolute; font-size: var(--size-note); line-height: 1.35; }

.pass-column { position: absolute; display: flex; flex-direction: column; }

/* The words themselves: dark on white and set in the display face, which is
   what the app uses wherever something is meant to be read rather than
   scanned. The gap is wide enough that two short words never read as one. */
.pass-plate {
	background: var(--ground);
	border-radius: var(--radius-field);
	padding: 11.7px;
	display: flex; flex-wrap: wrap;
	gap: 7.8px 11.7px;
	justify-content: center;
}
.pass-word { font-size: 20px; line-height: 1.2; color: var(--accent); }

.pass-again {
	align-self: flex-end;
	margin-top: 9.8px;
	color: var(--ink-inverse); font-size: 14px; line-height: 1;
	text-decoration: underline;
}
.pass-note { margin-top: 11.7px; font-size: var(--size-note); line-height: 1.35; }
.pass-none { font-size: var(--size-note); line-height: 1.35; }

.pass-label { margin-top: 19.5px; font-size: var(--size-display); line-height: 1; }
.pass-wrap {
	margin-top: 7.8px; height: 46.8px;
	background: var(--ground);
	border-radius: var(--radius-field);
	display: flex; align-items: center;
}
.pass-input {
	flex: 1; min-width: 0;
	border: none; outline: none;
	appearance: none;
	background: var(--ground);
	font-family: var(--font-body); font-size: 18px;
	color: var(--accent);
	padding: 0 7px;
}
.pass-input::placeholder { color: var(--ink-hint); }

/* Join and the line that stands in for it occupy the same place: one is drawn
   at a time, so the column does not jump when the retype comes good. */
.pass-join {
	margin-top: 15.6px; height: 46.8px;
	background: var(--accent); color: var(--ink-inverse);
	border-radius: var(--radius-soft);
	display: flex; align-items: center; justify-content: center;
	font-size: var(--size-display);
}
.pass-wait {
	margin-top: 15.6px; height: 46.8px;
	display: flex; align-items: center; justify-content: center;
	font-size: var(--size-note);
}

/* ── Streams feed ──────────────────────────────────────────── */

/* The meter sits 10.4px into the content area. Its parts are absolutely
   placed in the mockup's own paint order — see meter() in components.js. */
/* The feed screens are not lifted: they anchor top and bottom and scroll their
   own contents, so they have no foot to lose. These offsets used to ADD the
   lift back, which was right when the lift was a transform on the whole screen
   and cancelled out. It is now a margin on a scrolling canvas, and these
   screens have none — so adding it merely pushed the meter and the whole feed
   46.8px down the frame. */
.meter { color: var(--ink); }
.meter > * { position: absolute; }

.meter-badge {
	border-radius: var(--radius-pill);
	background: var(--accent);
	border: 2px solid var(--stroke-soft);
	display: flex; align-items: center; justify-content: center;
	color: var(--ink-inverse);
}

.meter-track { border-radius: 6.64px; background: var(--grey); }
.meter-balance { background: var(--good); }
.meter-notch { background: var(--stroke-soft); }

.meter-text { line-height: 1; white-space: nowrap; }
.meter-mark-dark  { color: var(--accent-alt); }
.meter-mark-light { color: var(--ink-inverse); }

/* The oxe mark: the arrows inside a ring. */
.oxe-mark {
	display: inline-flex; align-items: center; justify-content: center;
	border: 1.5px solid currentColor;
	border-radius: var(--radius-pill);
	flex: none;
}
.oxe-mark .glyph { width: 78%; height: 78%; }

.feed {
	position: absolute;
	left: 0; right: 0;
	top: 54.6px;		/* 163.8 in the frame, below the meter */
	bottom: 0;
	overflow-y: auto;
	overflow-x: hidden;
	scrollbar-width: none;
	-webkit-overflow-scrolling: touch;
}
.feed::-webkit-scrollbar { display: none; }

/* ── Feed card ─────────────────────────────────────────────── */

/* Cards alternate between two greys in the mockup, both off-white. */
/* Radii are drawn values, read off the rect's rx: 7.8 on the card, 3.9 on the
   plate the feature image sits on. spec.py did not report rx until it was taught
   to, so every corner in the app was square. */
.card {
	position: relative;
	width: var(--card-w);
	margin: 0 auto var(--gutter);
	background: var(--ground-raised);
	border-radius: var(--radius-control);
	overflow: hidden;
}
.card-alt { background: var(--grey); }
.tappable { cursor: pointer; }
.card-mark { color: var(--ink); }
.card-plate { background: var(--ground); border-radius: var(--radius-tile); }
.screen-notice { background: var(--ground); }
/* Anchored to the top of the content area, so it adds the lift back. */
.notice-slot { position: absolute; }
.card-plate .art { width: 100%; height: 100%; }
/* The plate's mark is a mask, and the mockup paints it indigo on the white. */
.card-plate .glyph { color: var(--accent); }
/* Both envelopes are drawn in indigo outline, sealed in the feed and open in
   Connect. Left to inherit, they came out black. */
.card-envelope { color: var(--accent); }

.card .art, .card .photo { width: 100%; height: 100%; }
.avatar-initials {
	background: var(--green); color: var(--ink-inverse);
	border-radius: 4px;
	display: flex; align-items: center; justify-content: center;
	font-size: 16.6px;
}
.card .avatar { border-radius: 4px; object-fit: cover; }

.card-sender { font-size: var(--size-label); line-height: 1; }
.card-time   { font-size: var(--size-meta); line-height: 1; }

.card-body {
	font-size: var(--size-body);
	/* The mockup sets the message solid: its two lines are 11.5 apart at 11.4px.
	   At 1.15 they were 13.1 apart, so the first line stood a line's tenth too
	   high and the pair covered more of the card than they are drawn to. */
	line-height: 1.01;
	max-height: 24px;
	overflow: hidden;
}

.card .feature { object-fit: cover; }
.card-caption {
	font-size: var(--size-meta); line-height: 1;
	white-space: nowrap;
}

.card-stat {
	display: flex; flex-direction: column; align-items: center;
	color: var(--accent);
	width: 12.5px;
}
.card-geo { width: 16.7px; }
/* A mark whose asset carries empty space: the box is the ink, and the oversized
   mask hangs inside it. See `inked` in components.js. */
.ink { position: relative; display: block; }
.stat-label {
	position: absolute; top: 18px;
	left: 50%; transform: translateX(-50%);
	font-size: var(--size-meta); line-height: 1;
	color: var(--ink);
	white-space: nowrap;
}

.price-value { position: absolute; font-size: var(--size-meta); color: var(--ink); line-height: 1; }

/* ── Chips ─────────────────────────────────────────────────── */

/* The mockup's chips stand 3.8 apart, not 4.3: measured across a three-chip row
   as the runs of card ground between them. */
.chip-row { display: flex; align-items: center; gap: 3.9px; }

.chip {
	height: 14px;
	/* A pill: the mockup's rx is half the chip's height, not a rounded corner.
	   Measured off the export by how far the fill is inset half a pixel down
	   from the top edge — 4 each side, which is r 6.5 to 7 rather than 4.2. */
	border-radius: 7px;
	display: inline-flex; align-items: center; gap: 2.4px;
	padding: 0 5.9px;
	font-size: var(--size-chip);
	line-height: 1;
	white-space: nowrap;
}
.chip-label { transform: translateY(-0.5px); }
.chip-kind  { background: var(--accent); color: var(--off-white); }
/* Connect draws the same kind chip in orange: the feed says what kind of thing
   is speaking, and in Connect everything speaking is a message. */
.feed-connect .chip-kind { background: var(--orange); }
.chip-scope { background: var(--orange); color: var(--off-white); }
.chip-tag   { background: var(--ground);  color: var(--ink-soft); }
.chip-eye   { flex: none; }

/* ── Sheets: the view menu, filters, compose settings ──────── */

/* A sheet's children carry content-area coordinates directly, so the body is
   the whole area and adds no offset of its own. It scrolls where the mockup
   screen is taller than the frame. */
.screen-sheet { background: var(--ground); }


.back-arrow { color: var(--accent-alt); }

.sheet-subject {
	font-size: var(--size-display);
	font-family: var(--font-body);
}

.section-head { position: absolute; }
.section-head h2 { font-size: var(--size-display); font-weight: var(--weight-regular); }
/* The mockup puts the blue line 24.8 below its heading's ink top and 11.3 in
   from the heading's left, every time; these are what those measure to once
   the line boxes either side of the ink are allowed for. */
.instruction {
	font-size: var(--size-note);
	color: var(--accent);
	margin-top: 2px;
	margin-left: 10.4px;
}

.sheet-block { position: absolute; }

/* Controls sit on a column pitch of 105.3 and a row pitch of 31.75 wherever the
   mockup lays them out in threes, so the grid is drawn rather than packed. */
.sheet-grid {
	display: grid;
	grid-template-columns: repeat(3, var(--pitch, 105.3px));
	row-gap: 11.7px;
}

/* ── Controls ──────────────────────────────────────────────── */

.radio-row { display: grid; grid-template-columns: repeat(3, var(--pitch, 105.5px)); }
.radio, .checkbox {
	display: inline-flex; align-items: center; gap: 6.65px;
	height: 20.05px;
	font-size: var(--size-label);
	cursor: pointer;
}
.radio input, .checkbox input { position: absolute; opacity: 0; pointer-events: none; }

.radio-mark {
	width: 20.05px; height: 20.05px; border-radius: var(--radius-pill);
	border: 2px solid var(--stroke);
	background: var(--ground);
	flex: none;
	position: relative;
}
.radio input:checked + .radio-mark::after {
	content: '';
	position: absolute; left: 50%; top: 50%;
	width: 9.16px; height: 9.16px; border-radius: var(--radius-pill);
	background: var(--accent);
	transform: translate(-50%, -50%);
}

.check-mark {
	width: 20.05px; height: 20.05px; border-radius: var(--radius-tile);
	border: 2px solid var(--stroke);
	background: var(--ground);
	flex: none;
	position: relative;
}
.checkbox input:checked + .check-mark::after {
	content: '';
	position: absolute; left: 50%; top: 50%;
	width: 5.2px; height: 9.6px;
	border: solid var(--stroke);
	border-width: 0 2px 2px 0;
	transform: translate(-50%, -55%) rotate(45deg);
}

/* ── Sliders ───────────────────────────────────────────────── */

.sl-row { position: absolute; }
.sl-plate { position: absolute; background: var(--ground-raised); border-radius: var(--radius-control); }
.sl-caption { position: absolute; font-size: var(--size-label); color: var(--ink); }
.sl-note {
	position: absolute; font-size: var(--size-chip); color: var(--accent);
	white-space: nowrap; text-align: right;
}
.sl-bar { position: absolute; background: var(--accent); border-radius: 2.35px; }
.sl-stop { position: absolute; background: var(--accent); border-radius: var(--radius-pill); }
/* Where a stop is a control, the hit area is a thumb's and the drawing inside it
   is still the 12.5 dot the mockup draws. Nothing about it is painted: the knob
   is what says which stop is chosen. */
.sl-pick { position: absolute; background: none; border: none; padding: 0; }
/* The knob is drawn over the stop it rests on, so it must not take the press
   that stop is there to receive. */
.sl-knob { position: absolute; background: var(--accent-alt); border-radius: var(--radius-pill);
	pointer-events: none; }
.sl-tri {
	position: absolute; width: 0; height: 0;
	border-left: 11.4px solid transparent;
	border-right: 11.4px solid transparent;
}
.sl-tri-lo { border-bottom: 13px solid var(--accent-alt); }
.sl-tri-hi { border-top: 13px solid var(--accent-alt); }

.box-label {
	display: block;
	font-size: var(--size-note);
	color: var(--accent);
	margin-bottom: 4px;
}

/* ── Filter rows ───────────────────────────────────────────── */

/* Each saved filter is its own rounded plate in the mockup, not a ruled row. */
.filter-row, .row {
	position: relative;
	height: 46.8px;
	display: flex; align-items: center; gap: 10px;
	padding: 0 15.6px;
	margin-bottom: 11.7px;
	background: var(--ground-raised);
	border-radius: var(--radius-soft);
	color: var(--ink);
}
.filter-name, .row-title { color: var(--ink); font-size: var(--size-row); flex: 1; }
.filter-name { font-family: var(--font-body); }
.filter-input {
	flex: 1; border: none; outline: none;
	font-size: 17.4px; color: var(--ink);
}
.filter-input::placeholder { color: var(--ink-hint); }
.filter-open, .row-next { color: var(--accent); margin-left: auto; }
.row-text { display: flex; flex-direction: column; flex: 1; }
.row-note { font-size: 9.4px; color: var(--accent); }

/* An interest group is drawn rather than packed: every offset in the row is the
   mockup's own, so nothing moves when a name gets longer. */
.row-interest { display: block; padding: 0; border-radius: var(--radius-control); }
.row-drop, .row-clear, .row-plus { color: var(--ink); }
.row-name { font-size: var(--size-row); color: var(--ink); }
.row-hint { font-size: var(--size-row); color: var(--ink-hint); }
.row-caret { background: var(--ink); }
.row-interest .row-next { margin-left: 0; }

/* The reach a geography filter is set to, centred under its slider. */
.sl-reach { position: absolute; font-size: var(--size-label); color: var(--accent); }

/* ── Compose ───────────────────────────────────────────────── */

.screen-compose { background: var(--ground); }

.blind {
	background: var(--grey-tile);
	border-radius: var(--radius-soft);
}
.blind-label { position: absolute; font-size: var(--size-row); color: var(--ink); line-height: 1; }

/* The blind rolls down into the compose settings; its pull is a chevron. */
.blind-chevron {
	position: absolute;
	border-left: 10.35px solid transparent;
	border-right: 10.35px solid transparent;
	border-top: 11.8px solid var(--accent);
	width: 0; height: 0;
}

/* Rolled down, the blind is a panel rather than a button and its chevron has
   turned over. The panel's own children carry coordinates against it. */
.blind-down { border-radius: var(--radius-control); }
.blind-roll { position: absolute; }
.blind-chevron-up {
	border-top: none;
	border-bottom: 11.8px solid var(--accent);
}
/* Inside the blind the interest rows sit on the panel's grey, not on white. */
.blind-down .row-interest { background: var(--ground-raised); }

/* Each mark is placed on the row where the mockup draws it, so the row is the
   plate the four sit on rather than a box that spaces them out. */
.tool { color: var(--accent); }
.tool .glyph { display: block; width: 100%; height: 100%; }

/* The message box is one grey plate: the field is not inset, it is the plate. */
.compose-box { background: var(--grey); border-radius: 3px; padding: 6px 11.7px 0; }
.compose-box .box-label { margin-bottom: 2px; }
.compose-text {
	width: 100%; height: 170px;
	border: none; outline: none; resize: none;
	background: none;
	font-family: var(--font-body); font-size: var(--size-row);
	padding: 3px 1.2px;
}
.compose-text::placeholder { color: var(--ink-hint); }

.compose-attached .box-label { margin-bottom: 4px; }
.attachments { display: flex; flex-wrap: wrap; gap: 8px; }
.attachment {
	position: relative;
	width: 157.9px; height: 46.8px;
	background: var(--lilac);
	border-radius: 4px;
	display: flex; align-items: center; justify-content: center;
	color: var(--accent);
}
.attach-drop { position: absolute; left: 15.6px; top: 15.6px; }

/* The same corner every button on the phone takes. It was left square, which is
   the one radius the mockup does not draw anywhere. */
.post-button {
	background: var(--accent); color: var(--ink-inverse);
	border-radius: var(--radius-control);
	display: flex; align-items: center; justify-content: center;
	font-size: var(--size-display);
}

/* ── Tags ──────────────────────────────────────────────────── */

/* Interests are drawn as white pills on a grey plate: 28.08 tall, fully
   rounded, 5.85 apart across and 33.93 from row to row. */
.tag-plate { position: absolute; background: var(--ground-raised); border-radius: var(--radius-control); }
.tag-field { position: absolute; display: flex; flex-wrap: wrap; column-gap: 5.85px; }
.tag-field > * { margin-bottom: 5.85px; }
.tag-pool { overflow: hidden; }
.tag {
	display: inline-flex; align-items: center;
	height: 28.08px;
	padding: 0 10.25px;
	border-radius: 14.04px;
	background: var(--ground);
	color: var(--ink-soft);
	font-size: 20.8px;
	line-height: 1;
}
/* A chosen interest carries the cross that drops it, so its right edge closes
   in on the mark rather than on the label. */
.tag-on { padding-right: 7.1px; gap: 8.5px; }
.tag-drop { color: var(--ink); flex: none; }

.tag-search {
	position: absolute;
	border: none; outline: none;
	background: none;
	font-size: var(--size-row);
	color: var(--ink);
	padding: 0;
}
.tag-clear { position: absolute; color: var(--ink); }
/* The pool scrolls. Its bar declares a magenta fill under a white stroke as
   wide as the bar itself, so what is painted — and what goes here — is white. */
.tag-scroll { position: absolute; background: var(--ink-inverse); border-radius: 1.95px; }

/* ── The pull-up: the app launcher ─────────────────────────── */

/* The sheet comes up over the content and the gesture strip, from 115 down the
   frame to its foot. It scrolls: `pullup_view_extended` draws the same sheet
   with every panel at an identical y and simply more of it, so the extra height
   is content to scroll to, not a second detent to open to. */
.pull-sheet {
	position: absolute;
	left: 0; right: 0; top: 128.5px; bottom: 0;
	background: var(--ground-sheet);
	/* Fitted to the corner profile: the edge is inset 5.5 one pixel below the top
	   and reaches the frame edge by 7 below it. */
	border-radius: var(--radius-sheet) var(--radius-sheet) 0 0;
	overflow: hidden;
	/* Above the dock's lip, because the dock's top lip IS this sheet stowed — that is
	   what the handle is drawn on. Sliding out from behind it is the whole
	   metaphor; coming up from the foot of the frame is not. */
	z-index: calc(var(--z-dock) - 1);
	/* Stowed, its top edge sits exactly at the dock's, so its lip is what the
	   dock shows and the first of a drag lifts it away from there.
	   How far that is depends on the height of the frame, which is the height of
	   the viewport — so it is measured and set by app.js. It was written here as
	   a constant, 845 - 86.2 - 128.5, which is right only on a viewport exactly
	   845 CSS px tall. Every real phone is shorter, and on those the sheet was
	   pushed below the gesture strip and out of sight, leaving the transparent
	   dock showing the white screen behind it. */
	transform: translateY(var(--sheet-travel, 630.3px));
	transition: transform var(--motion-slide) var(--ease-sheet);
	will-change: transform;
	pointer-events: none;
}
#frame[data-launcher="true"] .pull-sheet {
	transform: translateY(0);
	pointer-events: auto;
}
/* Raised, the sheet has the frame from 128.5 down, so the dock has nowhere to
   be. Its grip goes with it, which is why the sheet carries one of its own. */
#frame[data-launcher="true"] .dock { display: none; }
/* While a finger is on it the sheet follows that finger exactly, so the
   transition has to be out of the way. */
.pull-sheet.dragging { transition: none; }

/* The list scrolls; the sheet it sits in does not. */
.pull-body {
	position: absolute; inset: 0;
	overflow-y: auto; overflow-x: hidden;
	scrollbar-width: none;
}
.pull-body::-webkit-scrollbar { display: none; }
.pull-canvas { position: relative; width: 100%; height: 800px; }

/* The bar is drawn as a stroked line in the mockup, so what it paints is the
   stroke colour and not the fill the path declares. */
.pull-handle { position: absolute; background: var(--pale); border-radius: 3px; }
/* The one handle there is: drawn on the sheet, so it travels with it. Its
   reachable area is bigger than the 5px bar, and it sits above the scrolling
   list so it stays put while that moves. */
.pull-grip {
	position: absolute; left: 0; right: 0; top: 0; height: 34px;
	touch-action: none;
	z-index: 1;
}
/* The mockup draws the scroll position as a bar down the right-hand side. */
.pull-scroll {
	position: absolute;
	background: var(--ink-inverse);
	border-radius: 2.5px;
}
.pull-title, .pull-group {
	position: absolute;
	color: var(--ink-inverse); font-size: var(--size-display); line-height: 1;
	white-space: nowrap;
}
.pull-settings { position: absolute; }
.pull-settings .art { width: 100%; height: 100%; }

/* A group is a cyan panel; its tiles sit on top of it. */
.pull-panel {
	position: absolute;
	background: var(--accent-alt);
	border-radius: var(--radius-control);
}
.pull-tile {
	position: absolute;
	background: var(--accent);
	border-radius: 15.6px;
	overflow: hidden;
}
.pull-tile .art { width: 100%; height: 100%; }
.pull-label {
	position: absolute;
	color: var(--ink-inverse); font-size: var(--size-display); line-height: 1;
	text-align: center;
}

/* ── The pull-up's manage views ────────────────────────────── */

/* Pages of the same sheet, so everything below is placed against the sheet's
   own top edge exactly as the launcher's content is. */

.pull-back { position: absolute; color: var(--accent-alt); }
.pull-back .glyph { width: 100%; height: 100%; }

/* A pale row: the one list item these views are built from. */
.pull-row {
	position: absolute;
	background: var(--ground-raised);
	border-radius: var(--radius-control);
	color: var(--ink);
}
.pull-row > * { position: absolute; }
.pull-row-mark { color: var(--ink); }
.pull-row-mark .glyph { width: 100%; height: 100%; }
.pull-row-next { color: var(--accent); }
.pull-row-next .glyph { width: 100%; height: 100%; }
.pull-row-label {
	font-family: var(--font-body); font-size: var(--size-row); line-height: 1;
	color: var(--ink); white-space: nowrap;
}
/* A name being edited. It carries no chrome of its own: the row is the field. */
.pull-row-field {
	background: none; border: none; outline: none; padding: 0;
	caret-color: var(--ink);
}
.pull-row-field::placeholder { color: var(--ink-hint); opacity: 1; }
/* The caret the mockup draws in a field at rest, which gives way to the
   browser's own the moment the field is in use. */
.pull-caret { position: absolute; background: var(--ink); }

.pull-note {
	position: absolute;
	color: var(--ink-inverse); font-family: var(--font-body); font-size: var(--size-row);
	line-height: 1; white-space: nowrap;
}
.pull-caption, .pull-hint, .pull-eyebrow {
	position: absolute;
	font-family: var(--font-body); font-size: var(--size-note);
	line-height: 1; white-space: nowrap;
}
.pull-caption  { color: var(--ink-inverse); }
.pull-hint     { color: var(--off-white); }
.pull-eyebrow  { color: var(--accent-alt); }
.pull-group-name {
	position: absolute;
	color: var(--ink-inverse); font-family: var(--font-body); font-size: var(--size-display);
	font-weight: var(--weight-regular); line-height: 1; white-space: nowrap;
}

/* The app browser: a field, then the panel of tiles it filters. */
.pull-search {
	position: absolute;
	background: var(--ground-raised);
	border-radius: var(--radius-control);
}
.pull-search > * { position: absolute; }
.pull-search-field {
	background: none; border: none; outline: none; padding: 0;
	font-family: var(--font-body); font-size: var(--size-row); color: var(--ink);
	caret-color: var(--ink);
}
.pull-search-clear { color: var(--ink); }
.pull-search-clear .glyph { width: 100%; height: 100%; }
/* On the footer view the panel is drawn shorter than its contents, with a bar
   down its right-hand side saying so, so it scrolls within the page. */
.pull-panel-scrolls {
	overflow-y: auto; overflow-x: hidden;
	scrollbar-width: none;
}
.pull-panel-scrolls::-webkit-scrollbar { display: none; }
.pull-panel-bar { position: absolute; background: var(--ink-inverse); border-radius: 2px; }

/* A colour row. The mockup clips it part-way through the twelfth swatch, and
   the bar beneath marks the colour in use. */
.pull-swatch-row { position: absolute; overflow: hidden; }
.pull-swatch {
	position: absolute;
	border: 2px solid var(--stroke-soft);
	border-radius: var(--radius-tile);
}
.pull-swatch-bar {
	position: absolute;
	background: var(--pale);
	transition: left var(--motion-tap) ease;
}

/* The footer's five positions, on the yellow strip the mockup draws. */
.pull-positions { position: absolute; background: var(--yellow); }
.pull-slot {
	position: absolute;
	background: var(--accent);
	border-radius: var(--radius-control);
}
.pull-slot .art { position: absolute; }
.pull-slot-on   { background: var(--accent-alt); }
/* The app in hand rather than in a position: the mockup draws the first slot
   with no container of its own, so the strip shows through it. */
.pull-slot-bare { background: none; }

/* ── A capable app ─────────────────────────────────────────── */

/* An Oxegen-capable app is an ordinary third-party app that gets identity from
   Oxegen. It is drawn in its OWN colours, with a header it inserts above its
   view and Oxegen appearing over the top — so on these screens the magenta
   below the status bar belongs to the host, not to us, and neither the Oxegen
   app header nor the gesture strip is present. */
#frame[data-capable="true"] #screen { top: var(--status-h); bottom: 0; }
.screen-capable { background: var(--magenta); }
.host-header { position: absolute; background: var(--magenta-dim); }
/* The host app's own marks: a boxed cross, stroked in its light grey. */
.host-mark {
	position: absolute;
	border: 2px solid var(--grey); color: var(--grey);
	display: flex; align-items: center; justify-content: center;
}
.host-mark .glyph { width: 76%; height: 68%; }

/* ── Popups ────────────────────────────────────────────────── */

/* A popup runs from under the app header to 825.6 down the frame — past the top
   of the gesture strip at 782.7, which it covers. The content area normally
   stops at the dock, so a popup left inside it lost its last 43px and its two
   bottom corners to the strip. */
/* Raised over the dock the popup covers it, so the screen's own ground has to
   stop covering it too: the mockup draws the card standing over the gesture
   strip, with the strip showing to either side of it and below it, and a white
   ground painted the whole way down hid the strip on every popup screen. */
#frame[data-popup="true"] #screen {
	bottom: 0;
	z-index: calc(var(--z-dock) + 1);
	background: transparent;
}
#frame[data-popup="true"] .screen-panel { background: transparent; }
.screen-popup { background: var(--scrim); }
.screen-backpopup { background: transparent; }
.popup-card { background: var(--accent-alt); color: var(--ink); border-radius: var(--radius-popup); }
.popup-card .art { width: 100%; height: 100%; }
/* The closer and the plus are open paths stroked black. Their fill is lilac and
   sits behind the stroke where nobody can see it, which is what the old
   extractor reported and why both were drawn lilac. */
.popup-close { color: var(--ink); }
/* The heading is Graystroke and the rows are Liberation Sans. The two were the
   wrong way round: the display font is the mockup's heading face, not its
   body face. */
.popup-title {
	font-size: var(--size-display); font-weight: var(--weight-regular);
	font-family: var(--font-display);
}

/* Each row is a drawn tile, not a bare row: the deeper grey, 288.6 x 46.8, at
   the control radius. Its contents are placed at the mockup's own offsets
   within it. */
.share {
	position: absolute;
	left: 0; width: 100%; height: 46.8px;
	background: var(--grey-tile);
	border-radius: var(--radius-control);
	color: var(--accent);
	text-align: left;
}
.share-icon { position: absolute; left: 46.8px; top: 9.9px; }
.share-expiring .share-icon { color: var(--alert); }
.share-name {
	position: absolute; left: 115.7px;
	font-family: var(--font-body); font-size: var(--size-row); line-height: 1;
	color: var(--ink);
}
.share-terms {
	position: absolute; left: 115.7px;
	font-size: 9.4px; line-height: 1; color: var(--accent);
}
.share-stop { position: absolute; left: 15.6px; top: 15.6px; color: var(--ink); }
.share-next { position: absolute; left: 259.3px; top: 12px; color: var(--accent); }
.share-plus { position: absolute; left: 12.9px; top: 12.9px; color: var(--ink); }
.share-add .share-name { left: 46.8px; }

/* ── Starting a share ──────────────────────────────────────── */

/* Drawn on the cyan card, so the headings are black Graystroke and the settings
   are the board's grey plates with a white box at the end of each. The plates
   are --grey-tile here rather than the --grey-plate the checklist uses: a
   paler plate on cyan all but disappears, which is why the board draws two
   greys. */
.share-head { position: absolute; font-size: var(--size-display); line-height: 1;
	color: var(--ink); font-weight: var(--weight-regular); }
.share-field {
	position: absolute;
	background: var(--grey-tile);
	border-radius: var(--radius-control);
	text-align: left;
}
.share-pull {
	position: absolute;
	background: var(--ground);
	border-radius: var(--radius-control);
	display: flex; align-items: center; justify-content: center;
	color: var(--accent);
	pointer-events: none;
}
.share-pull .glyph { transition: transform var(--motion-turn) ease; }
.share-menu { background: var(--grey-tile); }
.share-menu-row { background: var(--grey-tile); border-top-color: var(--grey-plate); }
/* The app's usual hint grey, --ink-hint, is one value away from invisible on
   this plate's deeper grey -- the montage showed an empty field where the copy
   was. The board draws its own hint a step darker, which is --grey-soft. */
.share-field .check-hint { color: var(--grey-soft); }

.share-slider { position: absolute; }
.share-value {
	position: absolute; text-align: center;
	font-size: var(--size-label); line-height: 1; color: var(--ink-inverse);
}

.share-manage { position: absolute; color: var(--accent); }
.share-manage .glyph { position: absolute; }
.share-manage-text {
	position: absolute; text-align: center;
	font-size: var(--size-note); line-height: 1.1; color: var(--accent);
}

/* ── The oxegen menu ───────────────────────────────────────── */

#overlay {
	position: absolute; inset: 0;
	background: var(--scrim-deep);
	z-index: var(--z-overlay);
}
.menu {
	position: absolute;
	left: 0; right: 0; top: var(--status-h);
	background: var(--ground);
	padding: 14px 12px 20px;
	max-height: calc(100% - var(--status-h));
	overflow-y: auto;
}
.menu-group + .menu-group { margin-top: 16px; }
.menu-group-title { font-size: 18px; font-weight: var(--weight-regular); margin-bottom: 8px; }
.menu-tiles { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }
.tile { display: flex; flex-direction: column; align-items: center; gap: 6px; }
.tile-box {
	width: 58px; height: 58px;
	border-radius: 10px;
	background: var(--accent);
	color: var(--ink-inverse);
	display: flex; align-items: center; justify-content: center;
}
.tile-box .glyph, .tile-box .art { width: 55%; height: 55%; }
.tile-label { font-size: 11px; }

/* ── Stub screens ──────────────────────────────────────────── */

.screen-stub {
	display: flex; flex-direction: column; align-items: center; justify-content: center;
	gap: 8px; padding: 0 30px; text-align: center;
}
.stub-text { font-size: var(--size-label); }
.stub-note { font-size: var(--size-note); color: var(--accent); }
.stub-back {
	margin-top: 12px;
	background: var(--accent); color: var(--ink-inverse);
	padding: 10px 28px;
	font-size: var(--size-display);
}

/* ── Identity panel ────────────────────────────────────────── */

.panel { position: absolute; }
.panel-title {
	font-family: var(--font-display);
	font-size: var(--size-display); font-weight: var(--weight-regular); line-height: 1;
}

.identity-card {
	background: var(--yellow-soft);
	position: absolute;
	left: 0; top: 30.4px;
	width: 100%; height: 101px;
	border-radius: var(--radius-chip);
}
.identity-empty { background: var(--pale); }

/* An identity chip states what kind of identity it is, in that kind's colour. */
.chip-id {
	position: absolute; left: 11.7px; top: 11.9px;
	display: inline-flex; align-items: center; justify-content: center;
	width: 77.1px; height: 39px; border-radius: var(--radius-chip);
	font-size: var(--size-display);
}
.chip-huser-id { background: var(--accent); color: var(--ink-inverse); }
.chip-vuser-id { background: var(--yellow); color: var(--grey-dark); }
.chip-root-id  { background: var(--accent); color: var(--ink-inverse); }

/* Both measured within the card: the handle indented past the chip, and the
   note sitting at the foot. On the identity card the note is the "strictly
   anonymous" line and is set to the right; on the empty card it is the
   two-line instruction and runs from the left. */
.identity-handle {
	position: absolute; left: 22.9px; top: 52.2px;
	font-size: var(--size-row); line-height: 1;
}
.identity-note {
	position: absolute; left: 23px; top: 56.4px;
	font-size: var(--size-note); line-height: 1.2;
	color: var(--accent);
	width: 240px;
}
.identity-card:not(.identity-empty) .identity-note {
	left: auto; right: 12.2px; top: 78.9px; width: auto; text-align: right;
}
/* Creating a vuser is offered as a tile the size of an identity chip, in the
   network's green, with a white plus on it — not a bare plus beside a label. */
.create-vuser {
	position: absolute; left: 11.7px; top: 12px;
	display: inline-flex; align-items: center; gap: 11.7px;
	color: var(--accent); font-size: var(--size-row);
}
.create-vuser .vuser-tile {
	display: inline-flex; align-items: center; justify-content: center;
	width: 77.1px; height: 39px;
	border-radius: var(--radius-chip);
	background: var(--green); color: var(--ink-inverse);
	flex: none;
}

.wide-button {
	position: absolute;
	background: var(--accent); color: var(--ink-inverse);
	border-radius: var(--radius-control);
	display: flex; align-items: center; justify-content: center;
	font-size: var(--size-display);
}
/* The status panel's row sets its label nearer the plate's edge than a filter
   row does, and its arrow further from it: 10.2 and 17.5 against the 15.6 both
   sides a filter row uses. */
.panel-row { margin: 0; background: var(--ground-raised); border-radius: var(--radius-control); padding: 0 17.5px 0 10.2px; }
.net-line { font-size: var(--size-label); margin-bottom: 10px; }

/* What the network says about itself. */
.net-head { position: absolute; color: var(--ink); font-size: var(--size-display); line-height: 1; }
.net-text { position: absolute; font-size: var(--size-row); line-height: 1; color: var(--ink); }
.net-row {
	position: absolute;
	background: var(--ground-raised); border-radius: var(--radius-control);
	display: flex; align-items: center;
	padding: 0 11.7px;
	text-align: left;
}
.net-row-label { font-size: var(--size-row); color: var(--ink); }
.net-bullet { position: absolute; background: var(--ink); border-radius: var(--radius-pill); }
.net-row-next { margin-left: auto; color: var(--accent); }
/* An unselected type has no tile at all in the mockup; only a chosen one does. */
.net-type {
	position: absolute;
	border-radius: 5.9px;
	display: flex; flex-direction: column; align-items: center;
	padding-top: 5.8px;
	color: var(--ink-inverse);
}
.net-type-on { background: var(--good); }
.net-type-label { font-size: var(--size-note); line-height: 1; }
.net-type-mark { margin-top: 6px; color: var(--accent); }
.net-type-count { font-size: var(--size-note); line-height: 1; margin-top: 6px; }
.net-active { position: absolute; color: var(--accent); font-size: 29.1px; line-height: 1; }
.net-note { position: absolute; font-size: var(--size-label); line-height: 1; color: var(--ink-inverse); }
.popup-back { position: absolute; color: var(--accent); }
.net-slider { position: absolute; }
.sl-knob-pale { background: var(--off-white); }
/* Placeholder for the world map, which is not built. */
.net-map { position: absolute; background: var(--white-ghost); border-radius: var(--radius-control); }

/* ── Oxedize ───────────────────────────────────────────────── */

.screen-oxedize { background: var(--ground); }
.oxedize-sub  {
	position: absolute;
	font-family: var(--font-display);
	font-size: var(--size-display); line-height: 1;
}
.oxedize-name { position: absolute; font-size: var(--size-row); color: var(--accent); line-height: 1; }
.oxedize-note {
	position: absolute; color: var(--accent);
	font-size: var(--size-display); line-height: 1.15; text-align: center;
}
.screen-oxedize .art { width: 100%; height: 100%; }
.screen-oxedize .wordmark { color: var(--accent); }

/* The network takes its own time to answer, and the screen says so. */
@keyframes waiting-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.45; } }
.waiting { animation: waiting-pulse var(--motion-pulse) ease-in-out infinite; }

/* ── Pay ───────────────────────────────────────────────────── */

.screen-pay { background: var(--ground); }
.screen-pay .art { width: 100%; height: 100%; }

.pay-word   { position: absolute; font-size: 20.8px; color: var(--accent); line-height: 1; }
.pay-amount,
.pay-payee  { position: absolute; font-size: 33.3px; line-height: 1; font-weight: var(--weight-bold); }

/* The item sits on its own grey plate, with the column names above it on the
   white ground rather than inside it. */
.pay-item { position: absolute; }
.pay-plate {
	position: absolute;
	background: var(--ground-raised);
	border-radius: var(--radius-control);
}
.pay-col, .pay-cell { position: absolute; line-height: 1; }
.pay-col  { font-size: 9.4px;  color: var(--accent); top: 0; }
.pay-cell { font-size: var(--size-note); top: 22.7px; line-height: 1.03; }
.pay-col-item     { left:  12.0px; }
.pay-col-qty      { left: 199.9px; }
.pay-col-amount   { left: 307.1px; }
/* The item column is narrow enough that its name wraps, and does in the mockup. */
.pay-cell-item    { left:  11.7px; width: 90px; }
.pay-cell-qty     { left: 228.9px; }
.pay-cell-amount  { left: 301.2px; }

.pay-button {
	position: absolute;
	background: var(--accent);
	border-radius: var(--radius-control);
	overflow: hidden;
}

.pay-tick { color: var(--accent-alt); }
.pay-heading { position: absolute; color: var(--accent); font-weight: var(--weight-regular); font-size: var(--size-display); }

.pay-receipt { position: absolute; margin: 0; }
.pay-earned { position: absolute; font-size: var(--size-meta); color: var(--good); line-height: 1; }
.pay-debit  { position: absolute; font-size: 12.4px; color: var(--alert); line-height: 1; }
/* The oxecoin column's own grey, drawn nowhere else. */
.pay-oxes   { position: absolute; font-size: 18.6px; color: var(--grey-soft); line-height: 1; }

/* The two tiles at the head of the receipt: the app the payment was made in, and
   the payee's own mark. White plates on the receipt's own grey. */
.pay-tile {
	position: absolute;
	background: var(--ground);
	border-radius: var(--radius-tile);
	overflow: hidden;
}
.pay-tile-mark { position: absolute; color: var(--accent); }

/* Each amount on a pill of its own. The oxecoin figure sits on the network's
   yellow and the money debit on grey, which is what tells the two columns apart
   at a glance on a row this short. */
.pay-pill { position: absolute; }
.pay-pill-oxes  { background: var(--mint); border-radius: var(--radius-chip); }
.pay-pill-debit { background: var(--grey-hint); border-radius: var(--radius-control); }
/* The mark that says the yellow figure is in oxes and not in money. */
.pay-oxe-mark { position: absolute; color: var(--orange); }
/* A solid triangle pointing the way the money went, drawn rather than masked:
   it is five pixels of geometry and no asset carries it. */
.pay-arrow {
	position: absolute;
	background: var(--alert);
	clip-path: polygon(0 0, 100% 50%, 0 100%);
}
.pay-next { position: absolute; color: var(--accent); }

.pay-wash { color: var(--yellow-wash); }
.oxe-balance { position: absolute; color: var(--accent); }
.balance-label { position: absolute; font-size: var(--size-body); color: var(--ink); line-height: 1; }
.balance-value { position: absolute; font-size: 31.2px; line-height: 1; }
.balance-exact { position: absolute; font-size: 9.7px; color: var(--ink); line-height: 1; }
.balance-mark  { position: absolute; color: var(--accent-alt); }

.pay-notice {
	position: absolute; color: var(--accent);
	font-size: var(--size-display); line-height: 1.15; text-align: center;
}

/* The welcome the notes end the pay flow on. Its sibling sets one line of
   display type over the wash; this sets the same head and three lines of body
   under it, because it has something to say rather than a figure to show. */
.pay-welcome-head {
	position: absolute; color: var(--accent);
	font-size: var(--size-display); font-weight: var(--weight-regular);
	line-height: 1.15; text-align: center;
}
.pay-welcome-line {
	position: absolute; color: var(--ink);
	font-size: var(--size-label); line-height: 1.25; text-align: center;
}

/* ── Hubs: an app's own face ───────────────────────────────── */

/* A hub is a column of sections rather than a drawn screen: what is in it —
   how many identities, how many messages — is the member's and not a fixed
   number, so it flows instead of being pinned at coordinates. It still carries
   `.scrolls`, which is where the overflow and the finger's own scroller are. */
.screen-hub {
	background: var(--ground);
	padding: 15.6px 19.5px 78px;
}

.hub-section { margin-bottom: 23.4px; }
/* The heading carries the mockup's type and its absolute placement with it; in
   a column only the type is wanted. */
.hub-section .section-head { position: static; margin-bottom: 11.7px; }

/* The row is the target, end to end, so it is a button and takes the reset a
   button needs. */
.hub-row { width: 100%; text-align: left; }
.hub-row .row-title { font-size: var(--size-label); }
.hub-row .row-next { flex: none; }

/* An identity's chip on a hub row: the kind, in that identity's own colour,
   at a row's height rather than the panel's card height. */
.hub-chip {
	display: inline-flex; align-items: center; justify-content: center;
	width: 54.6px; height: 25.2px;
	border-radius: var(--radius-control);
	background: var(--yellow); color: var(--grey-dark);
	font-size: var(--size-chip);
	flex: none;
}
.hub-chip-huser { background: var(--accent); color: var(--ink-inverse); }
.hub-chip-new   { background: var(--green);  color: var(--ink-inverse); }

/* Eight pips, one per oxedation the network asks for. The gap after the fifth
   is the boundary between the two requirements, not decoration. */
.ox-pips { display: flex; align-items: center; gap: 7.8px; margin-bottom: 7.8px; }
.ox-pip {
	width: 15.6px; height: 15.6px; border-radius: var(--radius-pill);
	border: 2px solid var(--stroke);
	flex: none;
}
.ox-pip-on { background: var(--accent); }
.ox-pip-split { margin-left: 15.6px; }

.ox-tally { font-size: var(--size-note); color: var(--ink); margin-bottom: 11.7px; }

/* The message stack: the cards are the same cards the feed draws, and they are
   drawn to the frame's full width, so the section gives its padding back. */
.hub-notices { margin: 0 -19.5px; }
.hub-quiet { font-size: var(--size-note); color: var(--accent); }

/* ── Pay: the wallet ───────────────────────────────────────── */

/* The wallet's head is fixed and its ledger scrolls, which is the shape the
   feed already has: the running totals stay on screen while the list beneath
   them moves. */
.screen-wallet { background: var(--ground); }

/* The balance, set as the joining flow's last screen sets it, but centred in
   the frame rather than pinned: this figure changes with every spend, and a
   drawn left edge would slide the whole block sideways when it did. */
.wallet-balance {
	position: absolute;
	display: flex; flex-direction: column; align-items: center;
	color: var(--accent);
}
/* Those four carry the mockup's own type, and its own absolute placement with
   it. Here they are stacked, so only the placement is given back — all four of
   them, not only the ones that happen to be direct children: the figure and its
   mark sit in a row of their own, and left absolute the figure was laid over
   the mark rather than beside it. */
.wallet-balance .balance-label,
.wallet-balance .balance-value,
.wallet-balance .balance-exact,
.wallet-balance .balance-mark,
.wallet-figure { position: static; }
.wallet-figure { display: flex; align-items: center; gap: 5.2px; }
.wallet-balance .balance-value { margin: 3.9px 0 2.6px; }
.wallet-balance .balance-mark { width: 27.1px; height: 27.1px; }

/* The ledger scrolls under the heading, in the feed's own gutter and pitch. */
.ledger {
	position: absolute;
	left: 0; right: 0;
	top: 176.0px;
	bottom: 0;
	overflow-y: auto;
	overflow-x: hidden;
	scrollbar-width: none;
	-webkit-overflow-scrolling: touch;
}
.ledger::-webkit-scrollbar { display: none; }

.led-row { height: 50.4px; }
.led-mark { color: var(--accent); }
.led-memo {
	position: absolute;
	font-size: var(--size-label); line-height: 1;
	white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.led-when { position: absolute; font-size: var(--size-meta); line-height: 1; color: var(--accent); }

/* Each amount on a pill, as the receipt draws its two columns. A debit and a
   credit differ by their figure's colour on the same grey; a mint differs by
   the pill, because an oxecoin that did not exist before is not a transfer. */
.led-pill {
	position: absolute;
	right: 8.1px; top: 12.6px; height: 25.2px;
	padding: 0 9.1px;
	border-radius: 12.6px;
	background: var(--grey);
	display: flex; align-items: center; gap: 4.7px;
}
.led-amount { font-size: var(--size-label); line-height: 1; }
.led-debit  .led-amount { color: var(--alert); }
.led-credit .led-amount { color: var(--good); }
.led-mint   .led-pill   { background: var(--mint); }
.led-mint   .led-amount { color: var(--ink); }
.led-oxe { color: var(--orange); }

/* An empty ledger says what a ledger is for, rather than leaving the member to
   work out whether the app is broken. */
.ledger-empty { padding: 15.6px 19.5px; }
.ledger-empty-line { font-size: var(--size-label); line-height: 1.2; }
.ledger-empty-note {
	font-size: var(--size-note); color: var(--accent);
	line-height: 1.25; margin-top: 5.2px;
}

/* The identity card is taller than a phone's content area once the dock
   takes its share, so the panel scrolls rather than losing its last button. */
/* No padding-top here. It used to add the lift, which pushed the card 46.8px
   DOWN the frame — the opposite of what the lift is for, and enough to put
   every element of the panel out of place. */
.screen-panel { background: var(--ground); overflow-y: auto; }
.screen-panel .popup-card { margin-bottom: 20.7px; }
.screen-panel .popup-card { color: var(--ink); }
.screen-panel .art { width: 100%; height: 100%; }

/* ── Pull to refresh ───────────────────────────────────────── */

/* Pulled down, the meter turns over to the network's colour and the refresh
   mark shows beneath it — the mockup's streams_home_pulled_down. */
.refresh {
	position: absolute;
	left: 144.4px; top: 53.4px;
	width: 101.3px; height: 44.6px;
	color: var(--accent);
	opacity: 0;
	transition: opacity var(--motion-fade);
	pointer-events: none;
}
/* Connect has no meter above its list, so its refresh mark sits higher. This
   has to follow .refresh: declared before it, its top lost to the general
   rule and the mark was drawn behind the first card. */
.refresh-connect { top: 13.9px; height: 42.2px; }
.feed-connect { top: 0; }
.pulling .refresh { opacity: 1; }
/* Held open, the list rests clear of the mark, which is where
   connect_home_pull_to_refresh draws it. While a finger is on the cards the
   inline transform of the drag wins, so this is only the resting offset. */
.pulling .feed-connect { transform: translateY(71.5px); }
.pulling .meter-today,
.pulling .meter-session { background: var(--good); }
.refreshing .refresh { animation: waiting-pulse var(--motion-beat) ease-in-out infinite; }

.feed { transition: transform var(--motion-settle) ease-out; }
.pulling .feed { transition: none; }

/* ── Raised: the feed takes the frame ──────────────────────── */

/* Dragging the cards up gives the messages the whole frame — the app header,
   the meter and the gesture strip give way to recover the room, and come back
   when the feed is scrolled to its top again. `streams_home_pulled_up` is that
   state. */
#frame[data-raised="true"] .app-header,
#frame[data-raised="true"] .gestures,
#frame[data-raised="true"] .meter,
#frame[data-raised="true"] .refresh { display: none; }

/* The dock goes with them. `streams_home_pulled_up` draws the cards running to
   the foot of the frame — no gesture strip, and no lip above where it was
   either. The lip IS the stowed sheet, drawn by the sheet and translated to the
   dock's top edge, so hiding the dock alone leaves it standing across the room
   being recovered; both have to go, the way the typing state takes them. The way
   back is the way in: scrolling the feed to its top brings all of it back. */
#frame[data-raised="true"] .dock,
#frame[data-raised="true"] .pull-sheet { display: none; }

#frame[data-raised="true"] #screen {
	top: var(--status-h);
	bottom: 0;
}
#frame[data-raised="true"] .feed { top: 3.9px; }

/* ── Tap targets ───────────────────────────────────────────── */

/*
 * Every control carries an invisible hit area, centred on what it draws.
 *
 * The mockup draws marks, not buttons. A row's arrow is 11.8 x 20.7, a checkbox
 * 20 x 20, and the passphrase eye 30.7 x 8.1 — a third of what a thumb needs by
 * every published figure. Nothing here moves the drawing. Each control grows a
 * pseudo-element centred on its own box, at least --hit-w by --hit-h and never
 * smaller than the control itself, so the ink stays exactly where the mockup
 * puts it and the finger gets 44px.
 *
 * One mechanism, hung on the control TYPES rather than on a class, because the
 * app has no control factory to hang a class on: a control is `el('button', …)`
 * at the point it is used. The selector is the sweep's own list of what counts
 * as a control, so anything the sweep presses has a hit area by construction and
 * a control added later gets one without being remembered.
 *
 * A control that CONTAINS another control grows nothing: its hit area would be
 * painted over its own children and take them away. That is what `:has()` says
 * below — a row that carries a cross and an arrow is already 46.8 x 351 and
 * needs no help, and the cross inside it does. A checkbox's own `<input>` does
 * not count: it is invisible, it has `pointer-events: none`, and the label
 * around it is the control.
 *
 * Two rules govern the size:
 *
 *  - A hit area may never contain another control's centre. That is what the
 *    sweep asserts and what an over-eager target breaks: a 44px box that
 *    swallows its neighbour's centre has taken a control away.
 *  - Where the drawing is dense, the box is capped so that neighbouring hit
 *    areas MEET rather than overlap. A checkbox column is drawn at a row pitch
 *    of 31.75 and a chip row at a column pitch of 105.3; the cap is the pitch,
 *    less a 1px gutter so the boundary belongs to nobody.
 *
 * Each cap below is that pitch, measured, not chosen. 44 is Apple's HIG figure
 * and the largest of the three published ones, so a control that reaches it
 * clears Material's 48dp-in-density-pixels and WCAG 2.2 AA's 24px floor too.
 */
:where(#frame button, #frame a, #frame .checkbox, #frame .radio, #frame .tappable) {
	position: relative;
}
#frame :is(button, a, .checkbox, .radio, .tappable):not(:has(button, a, select,
	textarea, input:not([type="checkbox"]):not([type="radio"])))::after {
	content: '';
	position: absolute;
	left: 50%; top: 50%;
	width:  max(100%, var(--hit-w, 44px));
	height: max(100%, var(--hit-h, 44px));
	transform: translate(-50%, -50%);
}

/* A field cannot carry a pseudo-element, so it takes the height of the plate it
   is drawn on instead. Both of these are already centred in a 46.8 plate, so
   filling it moves no ink — but a field carries a white background of its own,
   and at full height that square-cornered white painted over the plate's rounded
   corners. The plate is what draws the ground; the field only sits on it. */
.pp-input, .filter-input { align-self: stretch; background: none; }

/*
 * The caps: each is the pitch the mockup draws, so that adjacent hit areas MEET
 * at the midpoint between the two controls and neither takes the other's ground.
 * A large gutter was tried first and is wrong — it puts a sliver between the two
 * that belongs to nobody, and the boundary then falls where the browser's own
 * touch correction happens to put it rather than halfway.
 *
 * Where a cap is 1.5 under the pitch rather than exactly on it, that is the
 * rounding allowance: the frame is scaled by a fraction and the drawn boxes
 * round to the device grid while a declared size does not, so two areas written
 * to meet exactly at 390 were found overlapping by 1.3 at 375.
 */
/* Checkboxes and radios are laid out on a row pitch of 31.75 wherever the
   mockup puts them in threes, which is what limits them — not the finger. */
.sheet-grid, .radio-row { --hit-h: 31.75px; }
/* An interest tag sits on a row pitch of 33.93 with a 5.85 gutter across, so
   there is room down the column and none along the row. */
.tag-field { --hit-h: 32.4px; --hit-w: 0px; }
/* A filter row draws its cross and its checkbox 27.8 apart, and the cross that
   clears a filter only 23.7 from the arrow that opens it — which is as much
   room as there is, and less than any published minimum. The drawing decides
   this one, not the finger. */
.filter-row { --hit-w: 27.8px; }
.filter-clear, .filter-open { --hit-w: 23.7px; }
/* The eye is drawn inside the passphrase field's own plate and its left edge is
   where the field ends, so it takes its room from above and below. */
.pp-wrap { --hit-w: 0px; }
/* "Forgot passphrase?" is drawn between Login and Join, 30.4 of clear air from
   the first and 31.4 from the second once both of their own plates are counted. */
.pp-forgot { --hit-h: 25px; }

/* Where a full-width grip and a small mark are drawn over one another, the mark
   takes the ground: the spanner is a 33.5px target and the grip is 390 wide, so
   the grip can spare it and it cannot spare the grip. */
.pull-settings { z-index: 2; }

/* ── The guides ────────────────────────────────────────────── */

/* Every guide is the same three things: a plate the slides will be drawn on,
   a title set in the mockup's script face, and the dots. The plate is empty
   because the mockup draws it empty — the slides are placeholders. */
.screen-guide { background: var(--ground); }
.guide-plate {
	position: absolute;
	background: var(--ground-raised);
	border-radius: var(--radius-card);
	touch-action: pan-y;
}
.guide-title { position: absolute; pointer-events: none; }
/* Drawn text, not a control: the title's lines are laid full-width so they can
   be centred, and left live they cover the plate a finger swipes across. */
.guide-line {
	position: absolute;
	pointer-events: none;
	font-family: var(--font-script);
	font-size: 34.2px;
	line-height: 1;
	color: var(--accent);
	text-align: center;
}
.guide-dot { position: absolute; border-radius: var(--radius-pill); background: var(--grey); }
.guide-dot-on { background: var(--accent-alt); }
.guide-skip {
	position: absolute;
	background: var(--accent); color: var(--ink-inverse);
	border-radius: var(--radius-control);
	display: flex; align-items: center; justify-content: center;
	font-size: var(--size-display);
}

/* ── The oxedation checklist ───────────────────────────────── */

.screen-check { background: var(--ground); }

.check-head { position: absolute; color: var(--ink); font-size: var(--size-display);
	font-weight: var(--weight-regular); line-height: 1; }
.check-sub  { position: absolute; color: var(--accent); font-size: var(--size-display);
	font-weight: var(--weight-regular); line-height: 1; }
.check-lock { position: absolute; color: var(--accent); }
/* The same rule: a run of drawn lines is laid to the width the mockup wraps it
   at, and anything under it has to stay reachable. */
.check-note, .check-failed, .check-text-block { pointer-events: none; }
.check-note { position: absolute; font-size: var(--size-note);
	color: var(--accent); line-height: 1; }
.check-failed { position: absolute; font-size: 15.6px; color: var(--alert); line-height: 1; }
.check-text-block { position: absolute; font-size: var(--size-row); color: var(--ink);
	line-height: 1; }

/* One capture: its two-line name, the medium, whose body part, and the mark. */
.check-row { position: absolute; text-align: left; }
/* Named apart from the shared `.check-label`, which is a checkbox's own text:
   one rule for two different things repainted every checkbox in the app. */
.cap-label { position: absolute; color: var(--accent); font-size: var(--size-display);
	line-height: 1; }
.check-media { position: absolute; color: var(--accent); font-size: 15.6px; line-height: 1; }
.check-of    { position: absolute; color: var(--accent); font-size: var(--size-body); line-height: 1; }
.check-mark-hand { position: absolute; color: var(--accent); }

/* The column of lights. Red is a capture not taken, green one that is, and
   amber the age check that could not answer either way. */
.lamp-panel { position: absolute; background: var(--accent); border-radius: var(--radius-card); }
.lamp { position: absolute; border-radius: var(--radius-pill); background: var(--alert); }
.lamp-on   { background: var(--good); }
.lamp-wait { background: var(--orange); }

/* A tick box or a round mark with its drawn lines beside it. The wrapper is a
   label of no size: its children carry the screen's own coordinates, and a tap
   on any of them reaches the input. */
.check-item { position: absolute; display: block; }
.check-item input { position: absolute; opacity: 0; pointer-events: none; }
.check-text { position: absolute; font-size: var(--size-row); color: var(--ink); line-height: 1; }
.check-hint { color: var(--ink-hint); }
.check-item .check-mark,
.check-item .radio-mark { position: absolute; }
.check-item input:checked + .check-mark::after {
	content: '';
	position: absolute; left: 50%; top: 50%;
	width: 5.2px; height: 9.6px;
	border: solid var(--stroke);
	border-width: 0 2px 2px 0;
	transform: translate(-50%, -55%) rotate(45deg);
}
.check-item input:checked + .radio-mark::after {
	content: '';
	position: absolute; left: 50%; top: 50%;
	width: 9.16px; height: 9.16px; border-radius: var(--radius-pill);
	background: var(--accent);
	transform: translate(-50%, -50%);
}

.check-plate { position: absolute; background: var(--ground-raised); border-radius: var(--radius-control); }
.check-next  { position: absolute; color: var(--accent); }
.check-clear { position: absolute; color: var(--ink); }

/* The evidence pulldown. One mark, drawn twice: stowed it points left, and it
   rotates downward on activation, which is what the notes describe. */
.check-pull {
	position: absolute;
	background: var(--pale);
	border-radius: var(--radius-control);
	display: flex; align-items: center; justify-content: center;
	color: var(--accent);
}
.check-pull .glyph { transition: transform var(--motion-turn) ease; }
.check-pull-open .glyph { transform: rotate(-90deg); }
.check-menu { position: absolute; background: var(--ground-raised); border-radius: var(--radius-control); }
.check-menu-row { position: absolute; text-align: left; border-top: 2px solid var(--stroke-soft); }

/* ── The camera ────────────────────────────────────────────── */

/* Full frame and no chrome: a viewfinder with three controls on the picture. */
.screen-camera { position: absolute; inset: 0; background: var(--black); overflow: hidden; }
.cam-feed, .cam-still { position: absolute; object-fit: cover; }
.cam-feed { width: 390px; height: 840.1px; left: 0; top: 0; }
.screen-camera.simulated .cam-feed { display: none; }
.screen-camera:not(.simulated) .cam-still { display: none; }
.screen-camera:not(.simulated) .cam-note { display: none; }
/* Said out loud, because a simulated capture that looked like a photograph
   would be the one dishonest thing in the flow. */
.cam-note, .cam-caption {
	position: absolute;
	pointer-events: none;
	text-align: center;
	font-size: var(--size-note);
	color: var(--ink-inverse);
	text-shadow: 0 1px 3px var(--black-drop);
}
.cam-shutter { position: absolute; border-radius: var(--radius-pill); background: var(--red); }
.cam-torch, .cam-close { position: absolute; color: var(--ink-inverse); }

/* ── The pairing code ──────────────────────────────────────── */
/* Drawn as vector rather than as a bitmap: the frame is scaled to whatever
   device it is on, and a code sized for one viewport is soft on every other.
   A soft QR code is a code the partner's camera has to work at. */

.qr-live { padding: 0; border: 0; background: none; display: block; }
.qr-art  { display: block; width: 100%; height: 100%; }

/* ── Verifying what the partner captured ───────────────────── */

.verify-tile { position: absolute; background: var(--ground-raised); border-radius: var(--radius-control); }
.verify-shot { position: absolute; border-radius: var(--radius-tile); object-fit: cover; }
.verify-play { position: absolute; color: var(--ink-inverse); }

/* ── Naming an identity ────────────────────────────────────── */

/* The label is written on the plate it will be worn on, so what is typed is set
   in the display face and on the colour the swatches below are choosing. */
.id-pill {
	position: absolute;
	border-radius: 17.55px;
	display: flex; align-items: center;
	padding: 0 15.6px;
	overflow: hidden;
}
.id-label {
	width: 100%;
	background: none; border: none; outline: none; padding: 0;
	font-family: var(--font-display); font-size: var(--size-display); line-height: 1;
	color: inherit; caret-color: currentColor;
}
.id-label::placeholder { color: currentColor; opacity: 0.45; }

/* The palette, at the swatch the manage-footer view already draws colours at. */
.id-swatches { position: absolute; }
.id-swatch {
	position: absolute;
	border: 2px solid var(--stroke-soft);
	border-radius: var(--radius-tile);
}
/* The one in use is ringed rather than moved: a swatch that grew would shift the
   row, and the row is what is being compared across. */
.id-swatch-on { border-color: var(--stroke); }

.id-kind-mark { position: absolute; color: var(--accent); }

/* A private-information row: the value where there is one, its name where there
   is not, and the arrow into the screen that sets it. */
.id-row { text-align: left; }
.id-row .check-next { pointer-events: none; }

.id-field {
	position: absolute; left: 11.7px; top: 0;
	width: 290px; height: 46.8px;
	background: none; border: none; outline: none; padding: 0;
	font-family: var(--font-body); font-size: var(--size-row); line-height: 1;
	color: var(--ink); caret-color: var(--ink);
}
.id-field::placeholder { color: var(--ink-hint); opacity: 1; }

/* ── The map ───────────────────────────────────────────────── */

.map-stub {
	position: absolute;
	background: var(--ground-raised);
	border-radius: var(--radius-control);
	display: flex; flex-direction: column; align-items: center; justify-content: center;
	gap: 8px; padding: 0 24px; text-align: center;
}

/* ── The card ──────────────────────────────────────────────── */

.screen-card { background: var(--ground); }
.card-title { position: absolute; color: var(--ink); font-size: var(--size-display);
	font-weight: var(--weight-regular); line-height: 1; }
.card-lock  { position: absolute; color: var(--accent); }
/* Drawn in its own colours: the mark's ring of circles fades away by opacity,
   which a single-colour mask cannot carry. */
.card-brands { position: absolute; }
.card-field { position: absolute; }
.field-label { position: absolute; font-size: var(--size-note); color: var(--accent);
	line-height: 1; }
.field-plate { position: absolute; background: var(--ground-raised); border-radius: var(--radius-control); }
.field-input {
	position: absolute; inset: 0;
	background: none; border: none; outline: none;
	padding: 0 11.7px;
	font-family: var(--font-body); font-size: var(--size-row); color: var(--ink);
}
.field-input::placeholder { color: var(--ink-hint); }
.cvc-mark { position: absolute; color: var(--accent-alt); }
.cvc-digits { position: absolute; font-size: 6.2px; color: var(--grey); line-height: 1; }
.pay-now {
	position: absolute;
	background: var(--accent);
	border-radius: var(--radius-control);
	display: flex; align-items: center; justify-content: center;
}
.pay-now-label { font-size: 16.6px; color: var(--ink-inverse); }
