Pair roles, not radios
The device screen answered the wrong question. A rider arriving at it has three: is the trainer on, will it take control, is the Click awake. A list sorted by signal strength answers none of them without being read, and it opened with a MAC address on every row — a number nobody types, acts on, or can tell from the one below it, and on Android a randomised one that changes anyway. So the setup comes first, as slots: Trainer, Shifter, Heart rate. Each says what is filling it, what is standing in the way, and the one action that would fix it. The list is still everything FR-9.1 asks for; it is just no longer the first thing to read. - FR-9.3 lives in the trainer tile: green only when connected *and* controlling, because a trainer that is attached and uncontrollable will not move the resistance. Its action then is Reconnect, since FTMS control is requested once at connect time — offering "acquire control" as a button the rider had failed to press would be a lie about what the protocol does. - Peripherals of no known role fold into a collapsed list. A scan in a flat picks up a dozen phones and a TV, and each was a full-height row between the rider and their trainer. The fold opens itself when no trainer has been identified at all, because a trainer that does not advertise FTMS until something connects classifies as unknown — that is the one case this must not swallow. - Kind is a glyph, identity is the name. Where two rows would otherwise be indistinguishable — a pair of pods, a room of "(no name)" — four characters of the address disambiguate them and nothing more. - Forget appears only on remembered devices. Forgetting a device that was never remembered is a no-op the rider had to read past on every row. - The Click panel is a repair manual, so it appears when there is something to repair. Its five-step drill used to be `open` by default in exactly the state riders hit most; it is now folded behind a summary, and its lede renders only while it is saying something the tile cannot. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+193
-245
@@ -10,25 +10,24 @@
|
||||
* required. Pairing both is not merely redundant: it is the configuration in
|
||||
* which the `−` pod stops reporting its own paddle.
|
||||
*
|
||||
* The `+` pod keeps a card, because it is the fallback that matters when the
|
||||
* `−` pod is flat or left in the garage — a rider with one working pod should
|
||||
* still get a working controller.
|
||||
* The `+` pod keeps a row of its own, because it is the fallback that matters
|
||||
* when the `−` pod is flat or left in the garage.
|
||||
*
|
||||
* They are named for the shift paddle each carries, not for the side of the
|
||||
* bar. Nothing a pod advertises says which end of the handlebar it is
|
||||
* clamped to, so left and right would be a guess; the paddle is printed on
|
||||
* the pod, and pressing it settles the question on screen (`confirmed`).
|
||||
* bar. Nothing a pod advertises says which end of the handlebar it is clamped
|
||||
* to, so left and right would be a guess; the paddle is printed on the pod,
|
||||
* and pressing it settles the question on screen (`confirmed`).
|
||||
*
|
||||
* The second job of this panel is to say what to *do* when nothing is
|
||||
* connected. A Click sleeps within seconds and only advertises while awake
|
||||
* (A-4), which no rider can guess from the words "not connected" — and which
|
||||
* is also why connecting is not a button they have to win a race with: the
|
||||
* running scan picks a pod up the moment it wakes and connects it (FR-1.5),
|
||||
* and once paired it goes back to the pod it knows. The buttons here are for
|
||||
* overriding that, not for driving it.
|
||||
* connected — a Click sleeps within seconds and only advertises while awake
|
||||
* (A-4), which no rider can guess from the words "not connected". But that is
|
||||
* the only thing worth a sentence, and only while it is true: connected, this
|
||||
* panel is two lines and a badge. The five-step drill it used to open with
|
||||
* lives behind a summary now, and the pods' MAC addresses are gone — they
|
||||
* identified nothing a rider could act on.
|
||||
*/
|
||||
import { app } from '../lib/app.svelte';
|
||||
import { api, type Pod, type PodState, type PodStatus } from '../lib/bridge';
|
||||
import { api, type Pod, type PodState } from '../lib/bridge';
|
||||
|
||||
const controller = $derived(app.controller);
|
||||
const pods = $derived(controller ? [controller.minus, controller.plus] : []);
|
||||
@@ -64,7 +63,7 @@
|
||||
|
||||
/** What each pod is for, so a rider who has lost one knows what they lost. */
|
||||
const PURPOSE: Record<Pod, string> = {
|
||||
minus: 'All ten buttons · relays the + pod',
|
||||
minus: 'All ten buttons',
|
||||
plus: 'Fallback · shift up, A B Y Z',
|
||||
};
|
||||
|
||||
@@ -80,15 +79,6 @@
|
||||
<section class="click">
|
||||
<header>
|
||||
<h2>Zwift Click</h2>
|
||||
<span class="summary" class:tone-ok={anyConnected} class:tone-warn={!anyConnected}>
|
||||
{#if minusLive}
|
||||
Connected · all ten buttons
|
||||
{:else if plusOnly}
|
||||
+ pod only
|
||||
{:else}
|
||||
Not connected
|
||||
{/if}
|
||||
</span>
|
||||
<div class="actions">
|
||||
{#if !scanning}
|
||||
<!-- Nothing can be picked up automatically while the scan is off, so
|
||||
@@ -96,9 +86,9 @@
|
||||
<button class="btn" onclick={() => app.run(() => api.startScan())}>Start scan</button>
|
||||
{:else if !minusLive}
|
||||
<!-- The − pod, not both: it is the one that carries the whole
|
||||
controller. The + pod has its own button on its own card. -->
|
||||
controller. The + pod has its own button on its own row. -->
|
||||
<button class="btn" disabled={busy} onclick={() => app.run(() => api.connectController())}>
|
||||
{busy ? 'Searching…' : 'Look for the − pod'}
|
||||
{busy ? 'Searching…' : 'Find − pod'}
|
||||
</button>
|
||||
{/if}
|
||||
{#if anyConnected}
|
||||
@@ -109,93 +99,74 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<p class="lede">
|
||||
{#if !scanning}
|
||||
<strong>The scan is off</strong>, so pods will not be picked up. Start it and press a button
|
||||
on a pod.
|
||||
{:else if minusLive}
|
||||
The <strong>− pod is connected</strong>, and it relays its twin: all ten buttons arrive over
|
||||
this one link. There is nothing to pair the + pod for. It reconnects on its own if it drops,
|
||||
and on the next launch.
|
||||
{:else if plusOnly}
|
||||
Running on the <strong>+ pod alone</strong> — its paddle, face buttons and shift-down on
|
||||
<span class="kbd">Y</span>. Press a button on the − pod to get the D-pad back.
|
||||
{:else}
|
||||
<strong>Press any button on the − pod.</strong> It only advertises while awake, and the
|
||||
running scan connects it as soon as it does — no need to press anything here.
|
||||
{/if}
|
||||
</p>
|
||||
<!-- Only while it is telling the rider something they cannot see. Connected,
|
||||
the badge above has already said it. -->
|
||||
{#if !scanning}
|
||||
<p class="lede"><strong>The scan is off</strong> — pods will not be picked up.</p>
|
||||
{:else if plusOnly}
|
||||
<p class="lede">
|
||||
Running on the <strong>+ pod alone</strong>: shift down with <span class="kbd">Y</span>.
|
||||
Press a button on the − pod for the D-pad.
|
||||
</p>
|
||||
{:else if !minusLive}
|
||||
<p class="lede">
|
||||
<strong>Press any button on the − pod.</strong> It only advertises while awake; the running
|
||||
scan connects it as soon as it does.
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
<div class="pods">
|
||||
{#each pods as pod (pod.pod)}
|
||||
{@const dormant = pod.pod === 'plus' && minusLive && pod.state !== 'connected'}
|
||||
<article class="pod" class:live={pod.state === 'connected'}>
|
||||
<div class="title">
|
||||
<span class="paddle" class:on={pod.state === 'connected'}>{pod.symbol}</span>
|
||||
<span class="what">
|
||||
<span class="label">{pod.symbol} pod</span>
|
||||
<span class="purpose">{PURPOSE[pod.pod]}</span>
|
||||
</span>
|
||||
<span class="state {STATE_TONE[pod.state]}">
|
||||
<span class="dot"></span>{STATE_TEXT[pod.state]}
|
||||
<!-- The paddle glyph is the pod's identity — big enough to match
|
||||
against the one printed on the hardware at arm's length. -->
|
||||
<span class="paddle" class:on={pod.state === 'connected'}>{pod.symbol}</span>
|
||||
|
||||
<div class="what">
|
||||
<span class="pod-name">{pod.symbol} pod</span>
|
||||
<span class="purpose">
|
||||
{#if dormant}
|
||||
<!-- Not a fault, and the panel must not let it read as one: this
|
||||
pod is idle because the − pod is already sending its buttons. -->
|
||||
Relayed by the − pod
|
||||
{:else if pod.confirmed}
|
||||
Confirmed — sent its own {pod.symbol} paddle
|
||||
{:else if pod.state === 'connected'}
|
||||
Press its {pod.symbol} paddle to confirm
|
||||
{:else}
|
||||
{PURPOSE[pod.pod]}
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<dl class="facts">
|
||||
<div>
|
||||
<dt>Battery</dt>
|
||||
<dd>{pod.batteryPercent != null ? `${pod.batteryPercent}%` : '—'}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Buttons seen</dt>
|
||||
<!-- Connected and silent looks exactly like working until you press
|
||||
something, so the count is the honest test of the link. -->
|
||||
<dd>
|
||||
{pod.buttonsSeen === 0 ? 'none yet' : `${pod.buttonsSeen}`}
|
||||
{#if pod.lastButton}<span class="last">· {pod.lastButton}</span>{/if}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Address</dt>
|
||||
<dd class="addr">{pod.address ?? '—'}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<span class="facts">
|
||||
{#if pod.batteryPercent != null}<span>{pod.batteryPercent}%</span>{/if}
|
||||
<!-- Connected and silent looks exactly like working until you press
|
||||
something, so the count is the honest test of the link. -->
|
||||
{#if pod.state === 'connected'}
|
||||
<span>{pod.buttonsSeen === 0 ? 'no presses yet' : `${pod.buttonsSeen} presses`}</span>
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
{#if pod.pod === 'plus' && minusLive && pod.state !== 'connected'}
|
||||
<!-- Not a fault, and the panel must not let it read as one: this pod
|
||||
is idle because the − pod is already sending its buttons. -->
|
||||
<p class="note tone-ok">
|
||||
Not needed — the − pod is relaying this pod's paddle and face buttons.
|
||||
</p>
|
||||
{:else if pod.confirmed}
|
||||
<p class="note tone-ok">Confirmed — this pod sent its own {pod.symbol} paddle.</p>
|
||||
{:else if pod.state === 'connected'}
|
||||
<p class="note">
|
||||
Press the <strong>{pod.symbol} paddle</strong> on this pod to confirm it is the one.
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
{#if pod.contradicted}
|
||||
<p class="note tone-warn">
|
||||
This pod sent the other paddle. If the pair is the wrong way round, swap them.
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
{#if pod.error}
|
||||
<!-- Verbatim (FR-9.2). Rust writes these as instructions, not codes. -->
|
||||
<p class="note tone-bad">{pod.error}</p>
|
||||
{/if}
|
||||
<span class="state {dormant ? 'tone-idle' : STATE_TONE[pod.state]}">
|
||||
<span class="dot"></span>{dormant ? 'Standing by' : STATE_TEXT[pod.state]}
|
||||
</span>
|
||||
|
||||
<div class="controls">
|
||||
{#if pod.state === 'connected' || pod.state === 'reconnecting'}
|
||||
<button class="btn ghost" onclick={() => disconnect(pod.pod)}>Disconnect</button>
|
||||
{:else if pod.state === 'searching'}
|
||||
<button class="btn ghost" onclick={() => disconnect(pod.pod)}>Stop searching</button>
|
||||
<button class="btn ghost" onclick={() => disconnect(pod.pod)}>Stop</button>
|
||||
{:else}
|
||||
<button class="btn ghost" onclick={() => connect(pod.pod)}>
|
||||
{pod.pod === 'plus' && minusLive ? 'Connect anyway' : 'Look for it now'}
|
||||
{dormant ? 'Connect anyway' : 'Find it'}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Verbatim (FR-9.2). Rust writes these as instructions, not codes. -->
|
||||
{#if pod.error}<p class="note tone-bad">{pod.error}</p>{/if}
|
||||
</article>
|
||||
{/each}
|
||||
|
||||
@@ -207,11 +178,9 @@
|
||||
{#if mixedUp || controller?.swapped}
|
||||
<div class="swap">
|
||||
<span>
|
||||
{#if mixedUp}
|
||||
A pod is sending the other pod's paddle — the pair may be filed the wrong way round.
|
||||
{:else}
|
||||
The pods are swapped from what their advertisement claims.
|
||||
{/if}
|
||||
{mixedUp
|
||||
? 'A pod is sending the other pod’s paddle — the pair may be filed the wrong way round.'
|
||||
: 'The pods are swapped from what their advertisement claims.'}
|
||||
</span>
|
||||
<button class="btn ghost" onclick={() => app.run(() => api.swapControllerPods())}>
|
||||
Swap + / −
|
||||
@@ -222,84 +191,62 @@
|
||||
{#if !minusLive}
|
||||
<!--
|
||||
FR-1.8. "Not connected" on its own reads as a broken app, and the real
|
||||
cause — a pod that is simply asleep — is something only the rider can fix,
|
||||
so it is spelled out here rather than left to be guessed at.
|
||||
cause — a pod that is simply asleep — is something only the rider can fix.
|
||||
The lede above says that much; the rest is for the rider it did not work
|
||||
for, and stays folded until they ask, because it is five paragraphs of
|
||||
things that are usually already true.
|
||||
-->
|
||||
<details class="help" open={!anyConnected}>
|
||||
<summary>The − pod will not connect — what to try</summary>
|
||||
<ol>
|
||||
<details class="help">
|
||||
<summary>Still not connecting?</summary>
|
||||
<ul>
|
||||
<li><strong>Press a button.</strong> A pod that is asleep does not advertise at all.</li>
|
||||
<li><strong>Keep the scan on.</strong> Auto-connect runs off it.</li>
|
||||
<li><strong>Close Zwift.</strong> One app at a time holds a pod.</li>
|
||||
<li><strong>Charge it.</strong> A flat pod stops advertising, and the app stops chasing.</li>
|
||||
<li>
|
||||
<strong>Press any button on the pod.</strong> This is almost always the whole answer.
|
||||
A Click sleeps within seconds and only advertises while awake, so a pod that is not
|
||||
broadcasting is the normal case, not a fault. The scan below is running: press a
|
||||
button and the pod connects itself, usually within a second or two.
|
||||
<strong>Or use the + pod</strong> — you lose the D-pad, <span class="kbd">Y</span> still
|
||||
shifts down.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Keep the scan on.</strong> Auto-connect works off the device scan, so a
|
||||
stopped scan means nothing gets picked up. Restart it above.
|
||||
The keyboard mirrors every Click action; <span class="kbd">?</span> lists them. A ride
|
||||
never depends on a pod.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Close anything else holding the pod.</strong> One app at a time — Zwift left
|
||||
running in the background keeps the link, and this app will never see the pod.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Bring it closer, or charge it.</strong> A flat pod stops advertising
|
||||
altogether, and after about thirty failed attempts the app stops chasing it and says
|
||||
so on the card.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Use the + pod instead.</strong> If the − pod is flat or not with you, the app
|
||||
falls back to the + pod on its own after about a minute — or press
|
||||
<em>Look for it now</em> on its card. You lose the D-pad; <span class="kbd">Y</span>
|
||||
still shifts down.
|
||||
</li>
|
||||
</ol>
|
||||
<p class="fallback">
|
||||
Meanwhile the keyboard mirrors every Click action — <span class="kbd">+</span>
|
||||
<span class="kbd">−</span> shift, <span class="kbd">↑</span>
|
||||
<span class="kbd">↓</span> trim the gradient. Press
|
||||
<span class="kbd">?</span> for the full list. A ride never depends on a pod.
|
||||
</p>
|
||||
</ul>
|
||||
</details>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.click {
|
||||
margin: 0 var(--edge) 0.8rem;
|
||||
padding: 0.9rem 1rem 1rem;
|
||||
margin: 0 var(--edge) 0.7rem;
|
||||
padding: 0.75rem 0.9rem 0.85rem;
|
||||
border-radius: 0.7rem;
|
||||
background: var(--bg-lift);
|
||||
}
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 0.75rem;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
font-size: 1.05rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
.summary {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.lede {
|
||||
margin: 0.55rem 0 0;
|
||||
font-size: 0.86rem;
|
||||
line-height: 1.5;
|
||||
color: var(--ink-soft);
|
||||
margin: 0.45rem 0 0;
|
||||
font-size: 0.84rem;
|
||||
line-height: 1.45;
|
||||
color: var(--ink-dim);
|
||||
}
|
||||
|
||||
.lede strong {
|
||||
color: var(--ink);
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
|
||||
.actions {
|
||||
@@ -308,43 +255,44 @@
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.pods {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr));
|
||||
gap: 0.6rem;
|
||||
margin-top: 0.8rem;
|
||||
.actions .btn {
|
||||
padding: 0.45em 0.8em;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.pod {
|
||||
.pods {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.55rem;
|
||||
padding: 0.8rem 0.9rem;
|
||||
border-radius: 0.55rem;
|
||||
border: 1px solid var(--hairline);
|
||||
gap: 0.3rem;
|
||||
margin-top: 0.6rem;
|
||||
}
|
||||
|
||||
/* A row, not a card: two pods stacked read as one controller with a fallback,
|
||||
which is what they are. Two equal cards read as two things to pair. */
|
||||
.pod {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.4rem 0.7rem;
|
||||
padding: 0.45rem 0.55rem;
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid transparent;
|
||||
background: rgba(255, 255, 255, 0.015);
|
||||
}
|
||||
|
||||
.pod.live {
|
||||
border-color: color-mix(in srgb, var(--ok) 35%, transparent);
|
||||
border-color: color-mix(in srgb, var(--ok) 30%, transparent);
|
||||
}
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.65rem;
|
||||
}
|
||||
|
||||
/* The paddle glyph is the pod's identity — big enough to match against the
|
||||
one printed on the hardware at arm's length. */
|
||||
.paddle {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 2.1rem;
|
||||
height: 2.1rem;
|
||||
width: 1.9rem;
|
||||
height: 1.9rem;
|
||||
border-radius: 0.45rem;
|
||||
background: var(--hairline);
|
||||
color: var(--ink-dim);
|
||||
font-size: 1.3rem;
|
||||
font-size: 1.15rem;
|
||||
font-weight: 300;
|
||||
line-height: 1;
|
||||
flex: none;
|
||||
@@ -358,96 +306,72 @@
|
||||
.what {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 1 10rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 1rem;
|
||||
/* Not `.label`: that is app.css's uppercase, letter-spaced section label,
|
||||
and a pod's name is a name, not a heading. */
|
||||
.pod-name {
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.purpose {
|
||||
font-size: 0.78rem;
|
||||
font-size: 0.76rem;
|
||||
color: var(--ink-dim);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.facts {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.8rem;
|
||||
color: var(--ink-dim);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.state {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4em;
|
||||
margin-left: auto;
|
||||
font-size: 0.88rem;
|
||||
font-size: 0.84rem;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.facts {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 0.4rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.facts div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.1rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-size: 0.72rem;
|
||||
letter-spacing: 0.03em;
|
||||
text-transform: uppercase;
|
||||
color: var(--ink-dim);
|
||||
}
|
||||
|
||||
dd {
|
||||
margin: 0;
|
||||
font-size: 0.9rem;
|
||||
color: var(--ink-soft);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.last {
|
||||
color: var(--ink-dim);
|
||||
}
|
||||
|
||||
.addr {
|
||||
font-size: 0.78rem;
|
||||
color: var(--ink-dim);
|
||||
}
|
||||
|
||||
.note {
|
||||
margin: 0;
|
||||
font-size: 0.84rem;
|
||||
color: var(--ink-soft);
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.note strong {
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
gap: 0.4rem;
|
||||
margin-top: auto;
|
||||
padding-top: 0.15rem;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.controls .btn {
|
||||
padding: 0.4em 0.7em;
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
.note {
|
||||
flex: 1 1 100%;
|
||||
margin: 0;
|
||||
font-size: 0.82rem;
|
||||
color: var(--ink-soft);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.swap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
margin-top: 0.6rem;
|
||||
padding: 0.55rem 0.75rem;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 0.5rem;
|
||||
padding: 0.45rem 0.7rem;
|
||||
border-radius: 0.5rem;
|
||||
background: rgba(255, 207, 74, 0.07);
|
||||
color: var(--ink-soft);
|
||||
font-size: 0.85rem;
|
||||
font-size: 0.83rem;
|
||||
}
|
||||
|
||||
.swap button {
|
||||
@@ -455,34 +379,58 @@
|
||||
}
|
||||
|
||||
.help {
|
||||
margin-top: 0.7rem;
|
||||
font-size: 0.86rem;
|
||||
color: var(--ink-soft);
|
||||
margin-top: 0.55rem;
|
||||
font-size: 0.83rem;
|
||||
color: var(--ink-dim);
|
||||
}
|
||||
|
||||
summary {
|
||||
cursor: pointer;
|
||||
color: var(--ink-soft);
|
||||
color: var(--ink-dim);
|
||||
font-weight: 600;
|
||||
min-height: var(--touch-min);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.help ol {
|
||||
margin: 0.5rem 0 0;
|
||||
padding-left: 1.2rem;
|
||||
/* `display: flex` on a summary drops the browser's own marker, and a
|
||||
disclosure with nothing to disclose-looking about it does not read as one. */
|
||||
summary::before {
|
||||
content: '';
|
||||
width: 0;
|
||||
height: 0;
|
||||
margin-right: 0.5rem;
|
||||
border-left: 5px solid currentColor;
|
||||
border-top: 4px solid transparent;
|
||||
border-bottom: 4px solid transparent;
|
||||
opacity: 0.6;
|
||||
transition: transform 120ms ease;
|
||||
}
|
||||
|
||||
details[open] > summary::before {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.help ul {
|
||||
margin: 0.2rem 0 0;
|
||||
padding-left: 1.1rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.help li {
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
.help strong {
|
||||
color: var(--ink);
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
|
||||
.fallback {
|
||||
margin: 0.5rem 0 0;
|
||||
color: var(--ink-dim);
|
||||
line-height: 1.6;
|
||||
/* Narrow: the state and the buttons drop below the pod's name rather than
|
||||
crushing it. */
|
||||
:global([data-size='compact']) .facts,
|
||||
:global([data-size='compact']) .state {
|
||||
order: 3;
|
||||
}
|
||||
|
||||
:global([data-size='compact']) .controls {
|
||||
order: 4;
|
||||
flex: 1 1 100%;
|
||||
margin-left: 2.3rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user