|
<div class="docs-content" data-render>
|
|
{% raw %}
|
|
# Responsiveness
|
|
|
|
The interface is designed to work from a 320px phone to a wide desktop without a separate mobile site. The same HTML reflows; only the CSS changes per breakpoint.
|
|
|
|
## The goal
|
|
|
|
- **One readable column on small screens.** Multi-column layouts collapse to a single fluid column; secondary rails are dropped, not squeezed.
|
|
- **No accidental horizontal scroll.** Long content (code, URLs, tables) is contained, never pushing the page wider than the viewport.
|
|
- **Comfortable touch targets.** On touch devices every interactive element is at least `44 x 44px`.
|
|
- **No iOS zoom-on-focus.** Form controls are at least `16px` on touch devices so Safari does not zoom when they are focused.
|
|
- **Respect device safe areas.** Floating controls keep clear of notches and home indicators with `env(safe-area-inset-*)`.
|
|
|
|
The viewport is declared once in `base.html` and must not be changed:
|
|
|
|
```html
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
```
|
|
|
|
## The standard breakpoint ladder
|
|
|
|
Use these four breakpoints. They are `max-width` (desktop-first refinements), and they are the ones the shared shell already targets, so a page that uses them stays in step with the nav, breadcrumb, and footer.
|
|
|
|
| Breakpoint | What changes |
|
|
|-----------|--------------|
|
|
| `1024px` | Multi-column layouts collapse to one column; the right rail is hidden; the top nav links collapse into the hamburger menu; the page padding tightens. |
|
|
| `768px` | Horizontal tab strips (feed tabs, project tabs) switch to a single scrolling row; labels shrink. |
|
|
| `480px` | Compact paddings; secondary actions hidden; post/detail typography steps down; `grid-2col` becomes one column; floating buttons honour safe-area insets. |
|
|
| `360px` | Headers allowed to wrap; timestamps drop to their own line; the comment avatar is hidden so the input keeps room. |
|
|
|
|
> A handful of one-off breakpoints (960, 900, 760, 720, 640, …) exist in older stylesheets. They are exactly the inconsistency this section exists to remove. **Do not add new ad-hoc breakpoints**: reach for `1024 / 768 / 480 / 360` first, and only introduce another value if a specific component genuinely breaks between two of them, with a comment saying why.
|
|
|
|
## How it is achieved
|
|
|
|
**Collapse a grid to one column.** This is the whole mobile story for a multi-column page:
|
|
|
|
```css
|
|
@media (max-width: 1024px) {
|
|
.feed-layout { grid-template-columns: 1fr; }
|
|
.feed-right { display: none; }
|
|
}
|
|
```
|
|
|
|
**Keep the fluid column from overflowing.** A grid/flex child does not shrink below its content unless told to. Every fluid main column sets:
|
|
|
|
```css
|
|
.feed-main, .leaderboard-main { min-width: 0; }
|
|
```
|
|
|
|
**Let tab strips scroll instead of wrapping or overflowing:**
|
|
|
|
```css
|
|
@media (max-width: 768px) {
|
|
.feed-nav { overflow-x: auto; flex-wrap: nowrap; -webkit-overflow-scrolling: touch; }
|
|
.feed-nav-btn { flex-shrink: 0; }
|
|
}
|
|
```
|
|
|
|
**Guarantee touch targets and prevent zoom.** One shared rule in `base.css` covers every page, so individual pages do not repeat it:
|
|
|
|
```css
|
|
@media (hover: none) and (pointer: coarse) {
|
|
.btn, .topnav-link, .sidebar-link, .post-action-btn, .profile-tab { min-height: 44px; }
|
|
.topnav-icon, .emoji-toggle-btn { min-width: 44px; min-height: 44px; }
|
|
.modal-card input, .modal-card textarea, .modal-card select { font-size: 16px; }
|
|
}
|
|
```
|
|
|
|
**Keep floating controls off the notch / home bar:**
|
|
|
|
```css
|
|
@media (max-width: 480px) {
|
|
.feed-fab {
|
|
bottom: calc(1rem + env(safe-area-inset-bottom));
|
|
right: calc(1rem + env(safe-area-inset-right));
|
|
}
|
|
}
|
|
```
|
|
|
|
## Rules
|
|
|
|
1. **Use the four standard breakpoints** (`1024 / 768 / 480 / 360`); do not invent new ones without a written reason.
|
|
2. **Every fluid grid/flex column sets `min-width: 0`.** This single line prevents almost all horizontal-overflow bugs.
|
|
3. **Collapse, do not cram.** Below `1024px` a page is one fluid column; drop side rails with `display: none` rather than shrinking them to slivers.
|
|
4. **Never override the viewport meta or the shared touch-target rule.** They are global; rely on them.
|
|
5. **Test the small end.** The hard cases are `360px` (wrapping) and long unbroken strings inside a card.
|
|
{% endraw %}
|
|
</div>
|
|
|
|
<style>
|
|
.sg-resp { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 0.625rem; }
|
|
.sg-resp .sg-cell { border: 1px solid var(--border-light); border-radius: var(--radius); background: var(--bg-card); padding: 0.75rem; font-size: 0.8125rem; color: var(--text-secondary); text-align: center; }
|
|
.sg-resp-note { font-size: 0.75rem; color: var(--text-muted); margin-top: 0.625rem; }
|
|
@media (max-width: 768px) {
|
|
.sg-resp { grid-template-columns: 1fr 1fr; }
|
|
}
|
|
@media (max-width: 480px) {
|
|
.sg-resp { grid-template-columns: 1fr; }
|
|
}
|
|
</style>
|
|
|
|
<div class="component-demo">
|
|
<div class="component-demo-title">Live reflow (three columns to two to one as the viewport narrows)</div>
|
|
<div class="sg-resp">
|
|
<div class="sg-cell">Item one</div>
|
|
<div class="sg-cell">Item two</div>
|
|
<div class="sg-cell">Item three</div>
|
|
<div class="sg-cell">Item four</div>
|
|
<div class="sg-cell">Item five</div>
|
|
<div class="sg-cell">Item six</div>
|
|
</div>
|
|
<p class="sg-resp-note">This grid uses the standard 768px and 480px breakpoints. Narrow the browser to watch it step from three columns to two to one.</p>
|
|
</div>
|