On a phone every control was outlined, hairlines ruled off the bars, corners were 7-9px and dialogs were centred cards: a desktop page, shrunk. Now, below 820px and in every theme, controls are filled and round (40px circles and capsules, pills for groups), the feed's name is a 26px large title, the tabs are a segmented control, rows are 44px with 17px text and an inset hairline, the reader's type is 17px, and dialogs are sheets from the bottom. --edge is the one switch for the outlines, left on for the high-contrast theme and for prefers-contrast:more. Glass's desktop radii moved into a min-width query, because at their specificity they outranked the phone's shapes, and on a phone its #51 rim only catches the top edge: all round a small capsule it read as an outline. Text boxes were 13.5px, and Safari zooms the page in on focus below 16px, so tapping search zoomed it; they are 16-17px on a phone now. Nothing read safe-area-inset-top, so opened from the home screen the top bar, the reader's back button and the drawer's head sat under the Dynamic Island. --safe-t pads them, and the phone's own top-bar rule, a shorthand, had also been discarding the side insets for the notch in landscape. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -19,11 +19,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
- On a phone, iPodderX looks like an iOS app: filled round buttons and capsules instead of
|
||||||
|
outlined boxes, no rules between the bars and the page, the feed's name as a large title, a
|
||||||
|
segmented control for the tabs, taller rows with 17px text, and dialogs as sheets from the
|
||||||
|
bottom. Tapping a text box no longer zooms the page in. High contrast, and Increase Contrast,
|
||||||
|
keep their outlines.
|
||||||
- The Glass theme's panels catch the light: a bright rim along the top and left edges, a fainter
|
- The Glass theme's panels catch the light: a bright rim along the top and left edges, a fainter
|
||||||
one along the far edges, and a soft sheen from the top-left corner.
|
one along the far edges, and a soft sheen from the top-left corner.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
- Opened from the home screen on an iPhone, the top bar, the reader's back button and the top
|
||||||
|
of the feed list keep clear of the Dynamic Island and the status bar instead of sitting under
|
||||||
|
them.
|
||||||
- Swiping to the next or previous item takes a longer swipe, a quarter of the screen, so a
|
- Swiping to the next or previous item takes a longer swipe, a quarter of the screen, so a
|
||||||
diagonal scroll no longer jumps ahead by accident. The item follows your finger and slides
|
diagonal scroll no longer jumps ahead by accident. The item follows your finger and slides
|
||||||
across as it changes.
|
across as it changes.
|
||||||
|
|||||||
124
web/app.css
124
web/app.css
@@ -504,11 +504,12 @@ a{color:var(--accent)}
|
|||||||
}
|
}
|
||||||
.iconbtn:hover{background:var(--raise);color:var(--fg)}
|
.iconbtn:hover{background:var(--raise);color:var(--fg)}
|
||||||
/* One toolbar across the window, as the original had: grouped buttons, search on the right. */
|
/* One toolbar across the window, as the original had: grouped buttons, search on the right. */
|
||||||
/* The display's own intrusions -- the home indicator along the bottom, the notch at one side in
|
/* The display's own intrusions -- the Dynamic Island and status bar along the top (#54), the
|
||||||
landscape. viewport-fit=cover hands the page the whole screen, which is what a standalone
|
home indicator along the bottom, the notch at one side in landscape. viewport-fit=cover hands the page the whole screen, which is what a standalone
|
||||||
shell and an iOS home-screen app both give it, so the bars along the edges pay for them in
|
shell and an iOS home-screen app both give it, so the bars along the edges pay for them in
|
||||||
padding. A browser with its own chrome reports nought and nothing moves. */
|
padding. A browser with its own chrome reports nought and nothing moves. */
|
||||||
:root{
|
:root{
|
||||||
|
--safe-t:env(safe-area-inset-top,0px);
|
||||||
--safe-b:env(safe-area-inset-bottom,0px);
|
--safe-b:env(safe-area-inset-bottom,0px);
|
||||||
--safe-l:env(safe-area-inset-left,0px);
|
--safe-l:env(safe-area-inset-left,0px);
|
||||||
--safe-r:env(safe-area-inset-right,0px);
|
--safe-r:env(safe-area-inset-right,0px);
|
||||||
@@ -518,7 +519,7 @@ a{color:var(--accent)}
|
|||||||
/* Longhand, and it has to stay longhand: the minifier runs a calc() in a padding shorthand
|
/* Longhand, and it has to stay longhand: the minifier runs a calc() in a padding shorthand
|
||||||
into the value after it -- `calc(12px + var(--safe-r))7px` -- and the browser then throws
|
into the value after it -- `calc(12px + var(--safe-r))7px` -- and the browser then throws
|
||||||
the whole declaration away, leaving the bar with no padding at all. */
|
the whole declaration away, leaving the bar with no padding at all. */
|
||||||
padding-top:7px;padding-bottom:7px;
|
padding-top:calc(7px + var(--safe-t));padding-bottom:7px;
|
||||||
padding-left:calc(12px + var(--safe-l));padding-right:calc(12px + var(--safe-r));
|
padding-left:calc(12px + var(--safe-l));padding-right:calc(12px + var(--safe-r));
|
||||||
background:var(--panel);border-bottom:1px solid var(--line);
|
background:var(--panel);border-bottom:1px solid var(--line);
|
||||||
}
|
}
|
||||||
@@ -966,7 +967,7 @@ body:has(#player.on) #status{padding-bottom:3px}
|
|||||||
|
|
||||||
@media (max-width:820px){
|
@media (max-width:820px){
|
||||||
#shell{grid-template-columns:1fr}
|
#shell{grid-template-columns:1fr}
|
||||||
#sidebar{position:fixed;inset:0 auto 0 0;width:min(300px,86vw);z-index:42;transform:translateX(-100%);transition:transform .2s;box-shadow:var(--shadow)}
|
#sidebar{position:fixed;inset:0 auto 0 0;padding-top:var(--safe-t);width:min(300px,86vw);z-index:42;transform:translateX(-100%);transition:transform .2s;box-shadow:var(--shadow)}
|
||||||
#sidebar.open{transform:none}
|
#sidebar.open{transform:none}
|
||||||
#scrim{position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:41}
|
#scrim{position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:41}
|
||||||
#player{
|
#player{
|
||||||
@@ -977,7 +978,10 @@ body:has(#player.on) #status{padding-bottom:3px}
|
|||||||
|
|
||||||
/* The feed list is reachable whether or not anything is playing. */
|
/* The feed list is reachable whether or not anything is playing. */
|
||||||
#burger{display:grid}
|
#burger{display:grid}
|
||||||
#topbar{gap:6px;padding:6px 8px}
|
#topbar{
|
||||||
|
gap:6px;padding-top:calc(6px + var(--safe-t));padding-bottom:6px;
|
||||||
|
padding-left:calc(8px + var(--safe-l));padding-right:calc(8px + var(--safe-r));
|
||||||
|
}
|
||||||
#topbar .grow,.tgroup.item{display:none}
|
#topbar .grow,.tgroup.item{display:none}
|
||||||
#topbar #epSearch{width:auto;flex:1;min-width:0}
|
#topbar #epSearch{width:auto;flex:1;min-width:0}
|
||||||
.tgroup button,.tgroup a{padding:4px 8px;min-width:32px}
|
.tgroup button,.tgroup a{padding:4px 8px;min-width:32px}
|
||||||
@@ -990,13 +994,13 @@ body:has(#player.on) #status{padding-bottom:3px}
|
|||||||
#files,.ephead,.ep .fd,.ep .file,.ep .size,.ep .rowacts{display:none}
|
#files,.ephead,.ep .fd,.ep .file,.ep .size,.ep .rowacts{display:none}
|
||||||
.ep,#split.one .ep{grid-template-columns:20px 20px minmax(0,1fr) auto}
|
.ep,#split.one .ep{grid-template-columns:20px 20px minmax(0,1fr) auto}
|
||||||
#grab{display:none}
|
#grab{display:none}
|
||||||
#detail{position:fixed;inset:0;z-index:38;display:none;border-top:0;padding:12px 16px 90px}
|
#detail{position:fixed;inset:0;z-index:38;display:none;border-top:0;padding:12px 16px 90px;padding-top:calc(12px + var(--safe-t))}
|
||||||
body.reading #detail{display:block}
|
body.reading #detail{display:block}
|
||||||
#dback{display:inline-block;margin-bottom:10px}
|
#dback{display:inline-block;margin-bottom:10px}
|
||||||
/* Beside the reader while it is swiped (gestures.ts): the item next to it, with a strip of the
|
/* Beside the reader while it is swiped (gestures.ts): the item next to it, with a strip of the
|
||||||
page's own background between them, so the list under the reader never shows (#52). */
|
page's own background between them, so the list under the reader never shows (#52). */
|
||||||
#dpeek{position:fixed;top:0;bottom:0;left:0;width:calc(100% + 16px);z-index:38;background:var(--bg);pointer-events:none}
|
#dpeek{position:fixed;top:0;bottom:0;left:0;width:calc(100% + 16px);z-index:38;background:var(--bg);pointer-events:none}
|
||||||
#dpeek>div{width:calc(100% - 16px);height:100%;overflow:hidden;padding:12px 16px 90px;background:var(--panel)}
|
#dpeek>div{width:calc(100% - 16px);height:100%;overflow:hidden;padding:12px 16px 90px;padding-top:calc(12px + var(--safe-t));background:var(--panel)}
|
||||||
#dpeek.next>div{margin-left:16px}
|
#dpeek.next>div{margin-left:16px}
|
||||||
#dpeek .encbox{margin:0 0 10px}
|
#dpeek .encbox{margin:0 0 10px}
|
||||||
#dpeek.end{display:flex;align-items:center;padding-left:28px;color:var(--faint);font-size:13px}
|
#dpeek.end{display:flex;align-items:center;padding-left:28px;color:var(--faint);font-size:13px}
|
||||||
@@ -1036,6 +1040,90 @@ body:has(#player.on) #status{padding-bottom:3px}
|
|||||||
#logbox .tg{display:none}
|
#logbox .tg{display:none}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---------- A phone, drawn the way iOS draws its own apps (#53) ---------- */
|
||||||
|
/* Filled shapes rather than outlines, corners that grow rounder as the shape grows and nest inside
|
||||||
|
each other, 44px to tap and 17px to read. The outlines stay where they are what makes a control
|
||||||
|
visible at all: the high-contrast theme, and anyone who has asked the system for more contrast.
|
||||||
|
--edge is the one switch for that. */
|
||||||
|
@media (max-width:820px){
|
||||||
|
:root{--edge:transparent}
|
||||||
|
:root[data-theme="contrast"]{--edge:var(--line)}
|
||||||
|
/* Glass rules its panels off with an edge of its own; the light along them (--glass-rim) stays. */
|
||||||
|
:root[data-theme="glass"],:root[data-theme="glass"][data-mode="light"]{--glass-edge:transparent}
|
||||||
|
}
|
||||||
|
@media (max-width:820px) and (prefers-contrast:more){
|
||||||
|
:root{--edge:var(--line)}
|
||||||
|
}
|
||||||
|
@media (max-width:820px){
|
||||||
|
/* The bars run into the page instead of being ruled off from it. */
|
||||||
|
#topbar{background:var(--bg);border-bottom-color:var(--edge);gap:8px}
|
||||||
|
#status{background:var(--bg);border-top-color:var(--edge)}
|
||||||
|
#player{border-top-color:var(--edge)}
|
||||||
|
.sidefoot{border-top-color:var(--edge)}
|
||||||
|
|
||||||
|
/* Buttons: filled and round. Hover is reset because Safari keeps it on whatever was last
|
||||||
|
tapped, which left an accent outline on it. */
|
||||||
|
.btn,.sidetools button,.sidefoot button,.tgroup,.toast,kbd,#logbox{border-color:var(--edge)}
|
||||||
|
.btn:hover,.sidetools button:hover,.sidefoot button:hover{border-color:var(--edge)}
|
||||||
|
.btn,.sidetools button,.sidefoot button{border-radius:999px;min-height:40px;padding-left:14px;padding-right:14px}
|
||||||
|
.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px}
|
||||||
|
.btn.ico,#dback{min-width:40px;height:40px;padding:0 10px;display:inline-grid;place-items:center}
|
||||||
|
.btn.tiny{min-height:0;padding:2px 9px}
|
||||||
|
.iconbtn{width:40px;height:40px;border-radius:999px;background:var(--panel2)}
|
||||||
|
.tgroup{border-radius:999px}
|
||||||
|
.tgroup button+button,.tgroup button+a{border-left-color:var(--edge)}
|
||||||
|
.tgroup button,.tgroup a{min-width:36px;height:40px;padding:0 8px}
|
||||||
|
|
||||||
|
/* Text boxes filled, and at 16px or more: Safari zooms the page in on one any smaller. */
|
||||||
|
input[type=search],input[type=text],input[type=password],input[type=number],select{
|
||||||
|
font-size:17px;background:var(--panel2);border-color:var(--edge);border-radius:10px;padding:9px 12px;
|
||||||
|
}
|
||||||
|
#topbar #epSearch{font-size:16px;border-radius:999px;padding:9px 14px}
|
||||||
|
input:focus,select:focus{border-color:var(--accent)}
|
||||||
|
|
||||||
|
/* The feed's name as the page's large title, the way Podcasts heads a show. */
|
||||||
|
.fhead h2,.fhead.slim h2{font-size:26px;font-weight:700;letter-spacing:-.01em;line-height:1.2}
|
||||||
|
.fhead .sub{font-size:15px}
|
||||||
|
.acts{gap:8px}
|
||||||
|
.acts .btn{min-width:44px;height:44px;padding:0 13px;justify-content:center;font-size:15px}
|
||||||
|
|
||||||
|
/* Tabs as a segmented control: equal segments on one track, the chosen one lifted. */
|
||||||
|
.tabs{flex:1 1 100%;border-radius:10px;padding:2px}
|
||||||
|
.tabs button,.tabs a{flex:1;text-align:center;padding:7px 4px;border-radius:8px;font-size:14px}
|
||||||
|
.tabs button.on,.tabs a.on{box-shadow:0 1px 3px rgba(0,0,0,.22)}
|
||||||
|
|
||||||
|
/* Rows: taller, larger type, and a hairline between them that starts where the text does. */
|
||||||
|
.ep{padding:10px 8px;border-radius:10px;min-height:44px}
|
||||||
|
.ep.sel{border-color:var(--edge)}
|
||||||
|
.ep:hover:not(.sel){background-color:transparent}
|
||||||
|
.ep .t{font-size:17px}
|
||||||
|
.ep .line,.ep .date{font-size:15px}
|
||||||
|
.ep+.ep{background-image:linear-gradient(var(--line),var(--line));background-size:calc(100% - 64px) 1px;background-position:64px 0;background-repeat:no-repeat}
|
||||||
|
.ep.sel,.ep.sel+.ep{background-image:none}
|
||||||
|
.feed{padding:9px 10px;min-height:44px;border-radius:10px}
|
||||||
|
.places{border-bottom-color:var(--edge);padding-bottom:10px;margin-bottom:10px}
|
||||||
|
.feed .txt b{font-size:17px}
|
||||||
|
.feed .txt small{font-size:13px}
|
||||||
|
|
||||||
|
/* The reader. */
|
||||||
|
.dt{font-size:26px;font-weight:700;line-height:1.2;letter-spacing:-.01em;margin-bottom:8px}
|
||||||
|
.dmeta{font-size:15px;gap:10px}
|
||||||
|
.encbox{border-color:var(--edge);border-radius:14px;padding:8px 8px 8px 14px}
|
||||||
|
.dbody{font-size:17px;line-height:1.55}
|
||||||
|
|
||||||
|
/* Dialogs are sheets, up from the bottom. */
|
||||||
|
#modal{place-items:end stretch;padding:0}
|
||||||
|
.card,.card.wide{
|
||||||
|
width:100%;max-height:calc(100dvh - var(--safe-t) - 12px);border-color:var(--edge);border-radius:20px 20px 0 0;
|
||||||
|
padding:20px 16px calc(20px + var(--safe-b));animation:sheet .3s cubic-bezier(.2,.8,.2,1);
|
||||||
|
}
|
||||||
|
.card h3{font-size:22px}
|
||||||
|
.field label,.field .hint{font-size:13px}
|
||||||
|
.check{font-size:17px}
|
||||||
|
.toast{border-radius:14px}
|
||||||
|
}
|
||||||
|
@keyframes sheet{from{transform:translateY(100%)}}
|
||||||
|
|
||||||
/* ---------- Classic: the 2004 Mac app ---------- */
|
/* ---------- Classic: the 2004 Mac app ---------- */
|
||||||
/* After the iPodderX screenshots: brushed-metal chrome, a pale blue-grey source list, Aqua blue
|
/* After the iPodderX screenshots: brushed-metal chrome, a pale blue-grey source list, Aqua blue
|
||||||
for whatever is selected, red unread badges, a striped table and Lucida Grande. */
|
for whatever is selected, red unread badges, a striped table and Lucida Grande. */
|
||||||
@@ -1137,7 +1225,6 @@ body:has(#player.on) #status{padding-bottom:3px}
|
|||||||
}
|
}
|
||||||
:root[data-theme="glass"] .card,
|
:root[data-theme="glass"] .card,
|
||||||
:root[data-theme="glass"] .toast{box-shadow:var(--glass-rim),var(--shadow)}
|
:root[data-theme="glass"] .toast{box-shadow:var(--glass-rim),var(--shadow)}
|
||||||
:root[data-theme="glass"] .card{border-radius:20px}
|
|
||||||
:root[data-theme="glass"] .toast{border-radius:14px}
|
:root[data-theme="glass"] .toast{border-radius:14px}
|
||||||
:root[data-theme="glass"] .toast.bad{border-color:var(--bad)}
|
:root[data-theme="glass"] .toast.bad{border-color:var(--bad)}
|
||||||
/* A narrow screen slides the feed list over the page, so it keeps its shadow. */
|
/* A narrow screen slides the feed list over the page, so it keeps its shadow. */
|
||||||
@@ -1149,12 +1236,25 @@ body:has(#player.on) #status{padding-bottom:3px}
|
|||||||
:root[data-theme="glass"] .tgroup,
|
:root[data-theme="glass"] .tgroup,
|
||||||
:root[data-theme="glass"] .tabs{
|
:root[data-theme="glass"] .tabs{
|
||||||
background:color-mix(in srgb,var(--panel2) 60%,transparent);
|
background:color-mix(in srgb,var(--panel2) 60%,transparent);
|
||||||
border-color:var(--glass-edge);border-radius:12px;box-shadow:var(--glass-rim);
|
border-color:var(--glass-edge);box-shadow:var(--glass-rim);
|
||||||
|
}
|
||||||
|
/* Rounder than the other themes; a phone has shapes of its own (#53), which these would outrank. */
|
||||||
|
@media (min-width:821px){
|
||||||
|
:root[data-theme="glass"] .card{border-radius:20px}
|
||||||
|
:root[data-theme="glass"] .tgroup,
|
||||||
|
:root[data-theme="glass"] .tabs{border-radius:12px}
|
||||||
|
:root[data-theme="glass"] .btn,
|
||||||
|
:root[data-theme="glass"] .sidetools button,
|
||||||
|
:root[data-theme="glass"] .sidefoot button{border-radius:10px}
|
||||||
}
|
}
|
||||||
:root[data-theme="glass"] .btn,
|
|
||||||
:root[data-theme="glass"] .sidetools button,
|
|
||||||
:root[data-theme="glass"] .sidefoot button{border-radius:10px}
|
|
||||||
:root[data-theme="glass"] .btn.primary{box-shadow:inset 0 1px 0 rgba(255,255,255,.35)}
|
:root[data-theme="glass"] .btn.primary{box-shadow:inset 0 1px 0 rgba(255,255,255,.35)}
|
||||||
|
/* On a phone the rim all round a small capsule or bar read as the outline #53 took away, so the
|
||||||
|
light only catches the top edge there, and the bars at the top and bottom have none. */
|
||||||
|
@media (max-width:820px){
|
||||||
|
:root[data-theme="glass"]{--glass-rim:inset 0 1px 0 var(--glass-lo)}
|
||||||
|
:root[data-theme="glass"] #topbar,
|
||||||
|
:root[data-theme="glass"] #status{box-shadow:none}
|
||||||
|
}
|
||||||
/* Someone who has asked the system for less transparency, or more contrast, gets solid panels. */
|
/* Someone who has asked the system for less transparency, or more contrast, gets solid panels. */
|
||||||
@media (prefers-reduced-transparency:reduce),(prefers-contrast:more){
|
@media (prefers-reduced-transparency:reduce),(prefers-contrast:more){
|
||||||
:root[data-theme="glass"] body{background:var(--bg)}
|
:root[data-theme="glass"] body{background:var(--bg)}
|
||||||
|
|||||||
Reference in New Issue
Block a user