Files
TREK/client/native-shell/shell.css
T
Maurice 52f0fb882f feat(native): redesign the app's first screens around a living landscape
The screens the app ships itself (welcome, plans, address, own server and
the states between) move from the dark card layout to a light design on an
animated aerial landscape drawn by a WebGL shader (native-shell/scenery.ts):
an island, a coastal town, farmland and mountains in one world.

- Welcome: the landscape with the TREK wordmark, three perks in one card,
  "Explore cloud plans" and "I already have a TREK" as two buttons.
- Plans: one tab per plan; choosing one flies the camera to its place,
  further inland with every plan. Each plan reads as a kind of trip, the
  price sits in a card with a monthly/yearly switch and counts to its new
  amount in the store's own format (native-shell/amount.ts), and a yearly
  price shows what it makes a month. Restore purchases, the renewal terms
  and the links stay as the store rules require.
- Address: the address writes itself on a glass card over the landscape;
  free, the password rules and the agreement sit on white cards.
- Own server: the server address on the glass card; a failed check turns
  the card red with what went wrong, proxy sign-in and client certificate
  panels in the same style.
- States: server not reachable, certificate needed, already subscribed,
  setting up (with its steps, then "Arrived") and the slow setup.
- iPad, an unfolded phone and a phone on its side get two columns, the
  landscape beside the content; on a dual-screen device each half takes
  one screen. Arabic and Hebrew mirror through logical sides only.
- The shader caps the pixel ratio at 2, drops to a lower resolution on a
  slow device, stops in the background and is one still frame with
  reduced motion.
- The sign-in and error page bars over a proxy's page (iOS and Android)
  take the same look: a white card and a dark button.
- Plus Jakarta Sans is bundled for the text, with Montserrat standing in
  for Cyrillic; the wordmark fonts and the old preview images are gone.
- New strings in all 28 languages; the keys of the old screens removed.
- The dev sample plans follow the cloud's plans (start, basis, plus, pro).
2026-11-12 14:18:42 +01:00

1415 lines
30 KiB
CSS

/*
* The first screens the app ships itself: welcome, plans, address, an own
* server, and the states between. Light, on a landscape seen from above
* (scenery.ts). Everything past them is the UI the chosen server delivers.
*
* Every side is a logical one (inline start and end), so Arabic and Hebrew
* mirror the layout without rules of their own.
*/
/*
* TREK's own type, self-hosted (latin subset, bundled: the shell loads offline
* and hotlinks nothing). Poppins sets the headlines and prices. Poppins has no
* Cyrillic, so Russian and Ukrainian get Montserrat under the same family name,
* loaded only when a Cyrillic letter is on screen.
*/
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url('./fonts/poppins-latin-500.woff2') format('woff2');
}
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url('./fonts/poppins-latin-600.woff2') format('woff2');
}
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url('./fonts/poppins-latin-700.woff2') format('woff2');
}
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url('./fonts/montserrat-cyrillic-500.woff2') format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url('./fonts/montserrat-cyrillic-600.woff2') format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url('./fonts/montserrat-cyrillic-700.woff2') format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/*
* Plus Jakarta Sans, the text face of the first screens: Latin and Latin
* Extended (Czech, Polish, Turkish, Vietnamese letters), each loaded only when
* a letter of its range is on screen. Cyrillic borrows Montserrat under the same
* family name, as Poppins does.
*/
@font-face {
font-family: 'Plus Jakarta Sans';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('./fonts/plus-jakarta-sans-latin-400.woff2') format('woff2');
unicode-range:
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC,
U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Plus Jakarta Sans';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url('./fonts/plus-jakarta-sans-latin-500.woff2') format('woff2');
unicode-range:
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC,
U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Plus Jakarta Sans';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url('./fonts/plus-jakarta-sans-latin-600.woff2') format('woff2');
unicode-range:
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC,
U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Plus Jakarta Sans';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url('./fonts/plus-jakarta-sans-latin-700.woff2') format('woff2');
unicode-range:
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC,
U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Plus Jakarta Sans';
font-style: normal;
font-weight: 800;
font-display: swap;
src: url('./fonts/plus-jakarta-sans-latin-800.woff2') format('woff2');
unicode-range:
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC,
U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Plus Jakarta Sans';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('./fonts/plus-jakarta-sans-latin-ext-400.woff2') format('woff2');
unicode-range:
U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Plus Jakarta Sans';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url('./fonts/plus-jakarta-sans-latin-ext-500.woff2') format('woff2');
unicode-range:
U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Plus Jakarta Sans';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url('./fonts/plus-jakarta-sans-latin-ext-600.woff2') format('woff2');
unicode-range:
U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Plus Jakarta Sans';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url('./fonts/plus-jakarta-sans-latin-ext-700.woff2') format('woff2');
unicode-range:
U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Plus Jakarta Sans';
font-style: normal;
font-weight: 800;
font-display: swap;
src: url('./fonts/plus-jakarta-sans-latin-ext-800.woff2') format('woff2');
unicode-range:
U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Plus Jakarta Sans';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url('./fonts/montserrat-cyrillic-500.woff2') format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
font-family: 'Plus Jakarta Sans';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url('./fonts/montserrat-cyrillic-600.woff2') format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
font-family: 'Plus Jakarta Sans';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url('./fonts/montserrat-cyrillic-700.woff2') format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
:root {
--page: #fffaf5;
--ink: #1f1a17;
--text: #6b5f57;
--muted: #8a7b6f;
--faint: #b9ab9e;
--line: #efe4d8;
--line-soft: #f3ebe1;
--sand: #f6efe6;
--dark: #111827;
--good: #1d7a46;
--bad: #b42318;
--sea: #cfe3ef;
--text-face: 'Plus Jakarta Sans', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
--display-face: 'Poppins', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
/* The insets of the device, from the web view or, on iOS, from the native side. */
--safe-top: max(env(safe-area-inset-top, 0px), var(--trek-native-safe-top, 0px));
--safe-bottom: max(env(safe-area-inset-bottom, 0px), var(--trek-native-safe-bottom, 0px));
--safe-start: max(env(safe-area-inset-left, 0px), var(--trek-native-safe-left, 0px));
--safe-end: max(env(safe-area-inset-right, 0px), var(--trek-native-safe-right, 0px));
color-scheme: light;
-webkit-text-size-adjust: 100%;
}
* {
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
}
html,
body {
margin: 0;
height: 100%;
background: var(--page);
color: var(--ink);
font-family: var(--text-face);
overscroll-behavior: none;
}
button,
input {
font: inherit;
}
:focus-visible {
outline: 2px solid var(--dark);
outline-offset: 2px;
}
/* The page: one screen at a time, each its own scroller. */
.shell {
position: fixed;
inset: 0;
overflow-x: clip;
}
.scr {
position: absolute;
inset: 0;
overflow-x: hidden;
overflow-y: auto;
scrollbar-width: none;
display: flex;
flex-direction: column;
padding-bottom: 30px;
background: var(--page);
}
.scr::-webkit-scrollbar {
display: none;
}
.scr.enter {
animation: scrIn 0.42s cubic-bezier(0.2, 0.8, 0.2, 1) both;
}
.scr.enter.reverse {
animation-name: scrInBack;
}
:root[dir='rtl'] .scr.enter {
animation-name: scrInBack;
}
:root[dir='rtl'] .scr.enter.reverse {
animation-name: scrIn;
}
@keyframes scrIn {
from {
opacity: 0;
transform: translateX(22px);
}
to {
opacity: 1;
transform: none;
}
}
@keyframes scrInBack {
from {
opacity: 0;
transform: translateX(-22px);
}
to {
opacity: 1;
transform: none;
}
}
/* On a phone the content flows in the screen's own column. */
.body {
display: contents;
}
/* ── The landscape at the top ── */
.hero {
position: relative;
flex: none;
overflow: hidden;
background: var(--sea);
margin-bottom: -1px;
height: min(300px, 36svh);
}
.hero.tall {
height: min(320px, 38svh);
}
.hero.welcome {
height: min(376px, 44.5svh);
min-height: 250px;
}
.hero .scenery {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
/* The status bar's clock and icons stay readable on the sea. */
.hero::before {
content: '';
position: absolute;
z-index: 1;
inset-inline: 0;
top: 0;
height: calc(var(--safe-top) + 36px);
background: linear-gradient(180deg, rgba(8, 24, 40, 0.28), rgba(8, 24, 40, 0));
pointer-events: none;
}
.hero::after {
content: '';
position: absolute;
inset-inline: 0;
bottom: -2px;
height: 110px;
background: linear-gradient(
180deg,
rgba(255, 250, 245, 0) 0%,
rgba(255, 250, 245, 0.8) 55%,
#fffaf5 82%,
#fffaf5 100%
);
pointer-events: none;
}
.hero.welcome::after {
height: 18px;
background: linear-gradient(180deg, rgba(255, 250, 245, 0), #fffaf5 88%);
}
.logo {
position: absolute;
z-index: 2;
top: calc(var(--safe-top) + 10px);
inset-inline-start: calc(24px + var(--safe-start));
height: 40px;
filter: drop-shadow(0 1px 6px rgba(7, 40, 70, 0.45));
}
.back {
position: absolute;
z-index: 3;
top: calc(var(--safe-top) + 8px);
inset-inline-start: calc(20px + var(--safe-start));
width: 40px;
height: 40px;
display: grid;
place-items: center;
border: 0;
border-radius: 999px;
background: #fff;
color: var(--ink);
cursor: pointer;
box-shadow: 0 6px 16px -8px rgba(0, 0, 0, 0.5);
}
:root[dir='rtl'] .back svg,
:root[dir='rtl'] .flip {
transform: scaleX(-1);
}
/* ── The glass card over the landscape ── */
.card {
position: absolute;
z-index: 2;
inset-inline: calc(20px + var(--safe-start)) calc(20px + var(--safe-end));
/* Lower in a short landscape (the keyboard is up), so it never meets the back button. */
bottom: clamp(18px, calc(100% - 242px), 78px);
padding: 12px 16px 14px;
border-radius: 18px;
background: rgba(255, 255, 255, 0.8);
-webkit-backdrop-filter: blur(16px) saturate(1.5);
backdrop-filter: blur(16px) saturate(1.5);
box-shadow:
0 18px 36px -18px rgba(17, 24, 39, 0.55),
inset 0 0 0 1px rgba(255, 255, 255, 0.6);
}
.card small {
display: flex;
align-items: center;
gap: 6px;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--text);
}
.card.ok small {
color: var(--good);
}
.card.bad small,
.card.bad .url i {
color: var(--bad);
}
.url {
display: flex;
align-items: center;
gap: 8px;
margin-top: 6px;
min-width: 0;
}
.url i {
display: flex;
flex: none;
color: var(--dark);
}
.url-text {
font-family: var(--display-face);
font-size: 22px;
font-weight: 600;
letter-spacing: -0.02em;
white-space: nowrap;
overflow: hidden;
min-width: 0;
}
.url-text b {
font-weight: 600;
color: var(--dark);
}
.url-text b.ph {
color: #a99a8c;
}
.url-text span {
color: var(--muted);
font-weight: 500;
}
.caret {
display: inline-block;
width: 2px;
height: 1.05em;
margin: 0 1px;
vertical-align: -0.17em;
background: var(--dark);
border-radius: 2px;
animation: blink 1.1s steps(1) infinite;
}
.card:not(.typing) .caret {
display: none;
}
@keyframes blink {
50% {
opacity: 0;
}
}
.spin {
width: 10px;
height: 10px;
border-radius: 50%;
border: 2px solid var(--text);
border-top-color: transparent;
animation: spin 0.8s linear infinite;
}
.cta .spin {
width: 16px;
height: 16px;
border-color: #fff;
border-top-color: transparent;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
/* ── Content ── */
.pad {
position: relative;
z-index: 2;
display: flex;
flex-direction: column;
gap: 14px;
padding-inline: calc(24px + var(--safe-start)) calc(24px + var(--safe-end));
}
.pad.grow {
flex: 1;
margin-top: -30px;
}
.pad.lift {
margin-top: -34px;
gap: 16px;
}
.pad.lift + .pad {
margin-top: 16px;
}
.stack {
display: flex;
flex-direction: column;
gap: 16px;
width: 100%;
}
.pad.form {
margin-top: 2px;
gap: 12px;
}
.push {
flex: 1;
min-height: 0;
}
h1 {
margin: 0;
font-family: var(--display-face);
font-weight: 600;
font-size: 36px;
letter-spacing: -0.025em;
line-height: 1.08;
text-wrap: balance;
}
.lead {
margin: 0;
font-size: 16px;
line-height: 1.55;
color: var(--text);
}
.title {
font-size: 26px;
letter-spacing: -0.02em;
}
.sub {
margin: -4px 0 0;
font-size: 14.5px;
line-height: 1.5;
color: var(--text);
}
.sub b {
color: var(--ink);
}
.perks {
display: flex;
flex-direction: column;
margin-top: 4px;
padding: 2px 16px;
border-radius: 18px;
background: #fff;
box-shadow:
0 0 0 1px var(--line),
0 14px 28px -24px rgba(31, 26, 23, 0.6);
}
.perks div {
display: flex;
align-items: center;
gap: 13px;
padding: 8px 0;
}
.perks div + div {
border-top: 1px solid var(--line-soft);
}
.perks i {
width: 34px;
height: 34px;
flex: none;
border-radius: 10px;
background: var(--dark);
color: #fff;
display: grid;
place-items: center;
}
.perks b {
display: block;
font-size: 14.5px;
font-weight: 800;
}
.perks small {
display: block;
font-size: 12.5px;
color: var(--muted);
margin-top: 1px;
}
.btns {
display: flex;
flex-direction: column;
gap: 8px;
}
/* ── Buttons ── */
.cta,
.cta2 {
width: 100%;
border: 0;
border-radius: 16px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
text-align: center;
text-decoration: none;
transition: transform 0.15s;
}
.cta {
min-height: 58px;
background: var(--dark);
color: #fff;
font: 700 17px var(--text-face);
box-shadow: 0 14px 28px -14px rgba(17, 24, 39, 0.55);
}
.cta2 {
min-height: 52px;
background: #fff;
color: var(--dark);
font: 700 16px var(--text-face);
box-shadow: 0 0 0 1.5px var(--ink);
}
.cta:active,
.cta2:active {
transform: scale(0.985);
}
.cta:disabled {
opacity: 0.7;
cursor: default;
}
.link {
display: block;
margin: 6px auto 0;
padding: 8px;
border: 0;
background: none;
color: var(--text);
font: 600 15px var(--text-face);
cursor: pointer;
}
.link.msg {
font-size: 12.5px;
color: var(--muted);
}
.dock {
position: sticky;
bottom: -30px;
z-index: 3;
margin-top: auto;
padding-block: 22px calc(30px + var(--safe-bottom));
padding-inline: calc(24px + var(--safe-start)) calc(24px + var(--safe-end));
background: linear-gradient(180deg, rgba(255, 250, 245, 0), #fffaf5 34%);
}
.dock .cta2 {
margin-top: 8px;
}
.fine {
margin: 10px 0 0;
text-align: center;
font-size: 12px;
font-weight: 500;
color: var(--muted);
}
.diag {
margin: 10px 0 0;
text-align: center;
font:
500 11px ui-monospace,
'SF Mono',
Menlo,
monospace;
color: #a99a8c;
overflow-wrap: anywhere;
}
/* ── Plans ── */
.tiers {
display: grid;
grid-auto-flow: column;
grid-auto-columns: minmax(0, 1fr);
gap: 4px;
padding: 4px;
border-radius: 16px;
background: var(--line-soft);
}
.tiers button {
border: 0;
border-radius: 12px;
padding: 10px 0;
background: transparent;
font: 700 14px var(--text-face);
color: var(--text);
cursor: pointer;
transition:
background 0.2s,
color 0.2s;
}
.tiers button[aria-checked='true'] {
background: #fff;
color: var(--ink);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
}
.trip {
min-width: 0;
margin-top: 10px;
}
.trip h1 {
font-size: 28px;
display: flex;
align-items: center;
gap: 10px;
white-space: nowrap;
}
.trip p {
margin: 6px 0 0;
color: var(--text);
font-size: 14px;
line-height: 1.4;
}
.badge {
font-family: var(--text-face);
font-size: 11px;
font-weight: 700;
letter-spacing: 0.04em;
padding: 4px 10px;
border-radius: 999px;
background: var(--dark);
color: #fff;
}
.pricebox {
display: flex;
flex-direction: column;
padding: 6px 6px 16px;
border-radius: 20px;
background: #fff;
box-shadow:
0 0 0 1px var(--line),
0 16px 30px -24px rgba(31, 26, 23, 0.55);
}
.cycle {
position: relative;
display: grid;
grid-template-columns: 1fr 1fr;
padding: 4px;
border-radius: 15px;
background: var(--sand);
}
.cycle .thumb {
position: absolute;
top: 4px;
bottom: 4px;
inset-inline-start: 4px;
width: calc(50% - 4px);
border-radius: 11px;
background: var(--dark);
box-shadow: 0 4px 10px -4px rgba(17, 24, 39, 0.5);
transition: transform 0.32s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.cycle[data-year='true'] .thumb {
transform: translateX(100%);
}
:root[dir='rtl'] .cycle[data-year='true'] .thumb {
transform: translateX(-100%);
}
.cycle button {
position: relative;
z-index: 1;
border: 0;
background: transparent;
padding: 9px 0;
font: 600 14px var(--text-face);
color: var(--text);
cursor: pointer;
transition: color 0.25s;
}
.cycle button[aria-checked='true'] {
color: #fff;
}
.price {
display: flex;
align-items: baseline;
justify-content: center;
gap: 8px;
margin-top: 18px;
}
.price b {
font-family: var(--display-face);
font-size: 40px;
font-weight: 600;
letter-spacing: -0.02em;
line-height: 1;
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
.price span {
color: var(--text);
font-size: 14px;
font-weight: 600;
}
.equals {
margin-top: 6px;
text-align: center;
font-size: 13px;
font-weight: 600;
color: var(--muted);
}
.equals:empty {
display: none;
}
.specs {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px;
}
.spec {
display: flex;
align-items: center;
gap: 10px;
min-width: 0;
padding: 10px 12px;
border-radius: 14px;
background: #fff;
box-shadow: 0 0 0 1px var(--line);
}
.spec i {
width: 32px;
height: 32px;
flex: none;
border-radius: 10px;
display: grid;
place-items: center;
background: var(--sand);
color: var(--dark);
}
.spec b {
display: block;
font-size: 14px;
font-weight: 800;
line-height: 1.2;
white-space: nowrap;
}
.spec small {
display: block;
font-size: 11.5px;
color: var(--muted);
font-weight: 600;
line-height: 1.25;
white-space: nowrap;
}
.spec.off i {
color: var(--faint);
}
.spec.off b {
color: var(--muted);
}
.spec.wide {
grid-column: 1 / -1;
}
.spec.wide em {
margin-inline-start: auto;
font-style: normal;
font-size: 11.5px;
font-weight: 700;
color: var(--text);
background: var(--sand);
padding: 4px 9px;
border-radius: 999px;
white-space: nowrap;
}
.legal {
margin: 2px 0 0;
font-size: 11.5px;
line-height: 1.5;
color: var(--muted);
text-align: center;
text-wrap: pretty;
}
.legal-links {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 4px 12px;
margin-top: -4px;
}
.legal-links a,
.agree a {
font-size: 11.5px;
font-weight: 600;
color: var(--text);
text-decoration: underline;
text-underline-offset: 2px;
text-decoration-color: #d9cbbb;
}
.loading {
display: grid;
place-items: center;
min-height: 160px;
}
.loading .spin {
width: 22px;
height: 22px;
}
/* ── Address and sign-up ── */
.plan-row {
display: flex;
align-items: center;
justify-content: space-between;
padding-block: 10px;
padding-inline: 16px 10px;
border-radius: 14px;
background: var(--sand);
}
.plan-row > span {
display: flex;
align-items: center;
gap: 10px;
min-width: 0;
}
.plan-row b {
font-size: 12.5px;
font-weight: 700;
letter-spacing: 0.02em;
color: #fff;
background: var(--dark);
padding: 4px 11px;
border-radius: 999px;
}
.plan-row small {
font-size: 13.5px;
font-weight: 600;
color: var(--text);
}
.plan-row button {
border: 0;
background: #fff;
border-radius: 10px;
padding: 7px 12px;
font: 700 13px var(--text-face);
color: var(--dark);
cursor: pointer;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.fld {
position: relative;
margin-top: 8px;
display: flex;
flex-direction: column;
gap: 2px;
padding: 10px 16px 11px;
border-radius: 16px;
background: #fff;
box-shadow: 0 0 0 1px var(--line);
cursor: text;
transition: box-shadow 0.2s;
}
.fld:focus-within {
box-shadow: 0 0 0 2px var(--dark);
}
.fld.bad {
box-shadow: 0 0 0 2px var(--bad);
animation: shake 0.35s;
}
@keyframes shake {
25% {
transform: translateX(-4px);
}
75% {
transform: translateX(4px);
}
}
.fld .lab {
font-size: 12px;
font-weight: 700;
color: var(--muted);
}
.fld .row {
display: flex;
align-items: center;
gap: 4px;
min-width: 0;
}
.fld input {
flex: 1;
min-width: 0;
border: 0;
outline: none;
background: transparent;
padding: 0;
font: 600 16px var(--text-face);
color: var(--ink);
}
.fld input::placeholder {
color: var(--faint);
}
.fld input:disabled {
opacity: 0.6;
}
.fld em {
font-style: normal;
font-weight: 600;
font-size: 15px;
color: var(--muted);
}
/* A badge on the card's edge: free, a rule met. */
.fld .edge {
position: absolute;
top: -10px;
inset-inline-end: 14px;
display: inline-flex;
align-items: center;
gap: 4px;
padding: 3px 9px;
border-radius: 999px;
font-size: 11px;
font-weight: 700;
color: var(--muted);
background: #f1e7db;
box-shadow: 0 0 0 3px var(--page);
transition:
background 0.2s,
color 0.2s;
}
.fld .edge.on {
color: #fff;
background: var(--good);
animation: pop 0.3s cubic-bezier(0.2, 0.8, 0.2, 1);
}
@keyframes pop {
from {
opacity: 0;
transform: translateY(4px);
}
to {
opacity: 1;
transform: none;
}
}
.fld .note {
margin: 4px 0 0;
font-size: 12.5px;
font-weight: 600;
color: var(--bad);
}
.fld .note:empty {
display: none;
}
.rules {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: 8px;
}
.rules span {
font-size: 11px;
font-weight: 700;
color: var(--muted);
background: var(--sand);
padding: 3px 9px;
border-radius: 999px;
transition:
background 0.2s,
color 0.2s;
}
.rules span.met {
color: #fff;
background: var(--good);
}
.eye {
display: flex;
padding: 2px;
border: 0;
background: transparent;
color: var(--muted);
cursor: pointer;
}
.agree {
margin: 2px 0 0;
font-size: 11.5px;
line-height: 1.5;
color: var(--muted);
text-align: center;
text-wrap: pretty;
}
.consent {
display: flex;
gap: 12px;
align-items: flex-start;
padding: 14px 16px;
border-radius: 16px;
background: #fff;
box-shadow: 0 0 0 1px var(--line);
font-size: 12.5px;
line-height: 1.5;
color: var(--text);
cursor: pointer;
}
.consent.bad {
box-shadow: 0 0 0 2px var(--bad);
animation: shake 0.35s;
}
.consent input {
appearance: none;
-webkit-appearance: none;
flex: none;
width: 22px;
height: 22px;
margin: 1px 0 0;
border-radius: 7px;
box-shadow: inset 0 0 0 2px #e6dccf;
background: #fff center / 14px no-repeat;
cursor: pointer;
transition:
background-color 0.2s,
box-shadow 0.2s;
}
.consent input:checked {
background-color: var(--dark);
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12.5l4.5 4.5L19 7.5'/%3E%3C/svg%3E");
box-shadow: none;
}
.consent strong {
display: block;
color: var(--ink);
font-weight: 800;
margin-bottom: 2px;
}
.consent-text {
display: block;
max-height: 140px;
overflow-y: auto;
white-space: pre-line;
}
/* ── Own server ── */
.notice {
display: flex;
gap: 10px;
align-items: flex-start;
margin: 0;
padding: 12px 14px;
border-radius: 14px;
background: #fbeeec;
color: #8f2a1d;
font-size: 13.5px;
font-weight: 600;
line-height: 1.45;
}
.notice:empty {
display: none;
}
.notice i {
display: flex;
flex: none;
margin-top: 1px;
}
.side {
display: flex;
align-items: center;
gap: 10px;
width: 100%;
padding: 13px 16px;
border: 0;
border-radius: 16px;
background: #fff;
box-shadow: 0 0 0 1px var(--line);
font: 600 14.5px var(--text-face);
color: var(--ink);
text-align: start;
cursor: pointer;
}
.side svg {
flex: none;
color: var(--dark);
}
.panel {
display: flex;
flex-direction: column;
gap: 10px;
padding: 16px;
border-radius: 18px;
background: #fff;
box-shadow:
0 0 0 1px var(--line),
0 16px 30px -26px rgba(31, 26, 23, 0.6);
}
.panel[hidden],
[hidden] {
display: none !important;
}
.panel-head {
display: flex;
gap: 12px;
align-items: center;
}
.panel-head i {
width: 36px;
height: 36px;
flex: none;
border-radius: 11px;
background: var(--dark);
color: #fff;
display: grid;
place-items: center;
}
.panel-head b {
font-size: 15px;
font-weight: 800;
line-height: 1.3;
}
.panel p {
margin: 0;
font-size: 13.5px;
line-height: 1.5;
color: var(--text);
}
.panel p b {
color: var(--ink);
}
.panel .panel-note {
font-size: 12px;
color: var(--muted);
}
.panel .panel-status {
color: var(--bad);
font-weight: 600;
}
.panel .panel-status:empty {
display: none;
}
.panel-btn {
min-height: 46px;
border: 0;
border-radius: 13px;
background: var(--dark);
color: #fff;
font: 700 15px var(--text-face);
cursor: pointer;
}
.panel-btn:disabled {
opacity: 0.7;
}
.panel-help {
display: inline-flex;
align-items: center;
gap: 4px;
align-self: flex-start;
font-size: 13px;
font-weight: 700;
color: var(--ink);
text-decoration: none;
}
.status-line {
margin: 0;
font-size: 13.5px;
font-weight: 600;
color: var(--text);
}
.status-line:empty {
display: none;
}
.status-line.bad {
color: var(--bad);
}
/* A panel needs the room: the landscape steps back while it is open. */
.hero.tall {
transition: height 0.45s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.scr.compact .hero.tall {
height: min(214px, 26svh);
}
.scr.compact .card {
bottom: 30px;
}
.scr.compact .sub {
display: none;
}
/* ── Setting up ── */
.steps {
display: flex;
flex-direction: column;
padding: 4px 16px;
border-radius: 18px;
background: #fff;
box-shadow: 0 0 0 1px var(--line);
}
.steps span {
display: flex;
align-items: center;
gap: 12px;
padding: 11px 0;
font-size: 14.5px;
font-weight: 700;
color: var(--faint);
transition: color 0.3s;
}
.steps span + span {
border-top: 1px solid var(--line-soft);
}
.steps span::before {
content: '';
flex: none;
width: 18px;
height: 18px;
border-radius: 50%;
box-shadow: inset 0 0 0 2px #e6dccf;
transition: all 0.3s;
}
.steps span.on {
color: var(--ink);
}
.steps span.on::before {
background: var(--good)
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12.5l4.5 4.5L19 7.5'/%3E%3C/svg%3E")
center / 12px no-repeat;
box-shadow: none;
}
/*
* Wide screens (an iPad, an unfolded phone) and a phone on its side: two
* columns, the landscape at full height on one side, the content beside it in
* a column of its own that scrolls on its own.
*/
@media (min-width: 900px), (orientation: landscape) and (max-height: 500px) {
.scr {
flex-direction: row;
padding: 0;
overflow: hidden;
}
.hero,
.hero.tall,
.hero.welcome,
.scr.compact .hero.tall {
flex: 1 1 auto;
height: 100%;
min-height: 0;
margin: 0;
}
.hero::after {
display: none;
}
.body {
flex: none;
width: 460px;
height: 100%;
display: flex;
flex-direction: column;
overflow-x: hidden;
overflow-y: auto;
scrollbar-width: none;
padding-top: calc(var(--safe-top) + 48px);
background: var(--page);
box-shadow: -1px 0 0 rgba(31, 26, 23, 0.06);
}
.body::-webkit-scrollbar {
display: none;
}
.pad.grow,
.pad.lift,
.pad.form {
margin-top: 0;
}
.pad.grow {
justify-content: center;
}
.pad.grow .push {
flex: 0 0 18px;
}
.scr.compact .sub {
display: block;
}
.card {
inset-inline: calc(32px + var(--safe-start)) auto;
width: min(420px, calc(100% - 64px));
bottom: 40px;
}
.logo {
top: calc(var(--safe-top) + 24px);
inset-inline-start: calc(32px + var(--safe-start));
height: 46px;
}
.back {
top: calc(var(--safe-top) + 20px);
inset-inline-start: calc(28px + var(--safe-start));
}
.pad,
.dock {
padding-inline: 24px calc(24px + var(--safe-end));
}
}
/* A phone on its side has little height: the column is narrower and starts higher. */
@media (orientation: landscape) and (max-height: 500px) {
.body {
width: min(420px, 52vw);
padding-top: calc(var(--safe-top) + 20px);
}
}
/* Two screens with a hinge between them: the landscape on one, the content on the other. */
@media (horizontal-viewport-segments: 2) {
.hero,
.hero.tall,
.hero.welcome,
.scr.compact .hero.tall {
flex: none;
width: env(viewport-segment-width 0 0);
}
.body {
width: env(viewport-segment-width 1 0);
margin-inline-start: calc(env(viewport-segment-left 1 0) - env(viewport-segment-right 0 0));
}
}
@media (prefers-reduced-motion: reduce) {
.scr.enter,
.scr * {
animation: none !important;
transition: none !important;
}
}