1. Mobile-First Design Philosophy for Psilobase

Harm-reduction portals occupy a uniquely demanding position in the web landscape. Unlike a recipe site or a news feed, Psilobase is often consulted at moments of genuine need — a person may be mid-experience, seeking dosage context, contraindication information, or grounding techniques, and the only device in reach is a phone. The stakes of a broken layout are therefore not merely aesthetic: a navigation menu that fails to open at 375 px, a dosage table that overflows its container, or a safety protocol that requires pinch-zooming to read can mean a person cannot access critical harm-reduction information when they need it most.

This context demands a rigorous mobile-first development strategy rather than the historically common desktop-first approach where mobile was treated as an afterthought requiring media-query overrides. Mobile-first means writing the baseline CSS for the narrowest supported viewport — 320 px, which covers the iPhone SE (first generation) and many budget Android devices — and then layering in enhancements as the viewport widens. The resulting cascade is simpler, smaller in file size, and far less prone to specificity conflicts that cause unexpected regressions on small screens.

Progressive Enhancement Approach

Progressive enhancement for Psilobase means the core content — species identification, dosage reference, safety checklists, integration guidance — is accessible with no JavaScript, no custom fonts, and a CSS file that is a few kilobytes. A visitor using a very slow 3G connection, an older Android browser, or a screen reader on a refurbished device should still be able to read every word of a safety protocol. JavaScript layers on top: the hamburger menu toggle, the dosage calculator, the interactive protocol selector. If a script fails to load, content remains visible and links remain functional.

Practically this means:

  • Semantic HTML first. Every piece of content has meaning without CSS. Headings create hierarchy, lists convey enumeration, tables present relational data. A raw unstyled page should still be usable.
  • CSS cascade flows upward. Base styles target mobile. @media (min-width: 768px) blocks handle tablets. @media (min-width: 1024px) blocks handle desktops. No max-width overrides needed to "undo" desktop rules for mobile.
  • JavaScript is an enhancement. The hamburger menu is visible and operable via a focusable <button> even before JavaScript parses. The dosage calculator degrades to a static reference table. Forms submit without AJAX if JavaScript is disabled.
  • Font loading is non-blocking. System font stack as immediate fallback; web fonts load asynchronously with font-display: swap. No invisible text flash on slow connections.

Why Phones Are the Primary Target

Analytics across similar harm-reduction portals consistently show 65–75% of sessions originating from mobile devices, with a meaningful portion occurring between 10 pm and 4 am — precisely the hours when recreational or therapeutic psilocybin experiences are most common. The implication is that many visitors are not leisurely browsing; they are looking for specific information quickly, possibly in dim environments, possibly with altered color perception or fine motor control. Large tap targets (minimum 44 × 44 px as per Apple HIG and WCAG 2.5.5), high-contrast text, simple navigation, and layouts that do not require horizontal scrolling are not optional refinements — they are fundamental to the portal's harm-reduction mission.

2. Breakpoints and What Changes at Each Width

Psilobase uses five primary breakpoints, selected not by arbitrary device dimensions but by the natural reflow points of the content itself. The following describes the target viewport width and the specific layout changes that occur at each threshold.

320 px — Small Phones (iPhone SE 1st gen, budget Android)

This is the baseline — all CSS targets this width unless a media query says otherwise. At 320 px:

  • Navigation collapses to a hamburger button; the full nav-menu is hidden and toggled via JavaScript (aria-expanded toggles on the button).
  • The site logo text is shortened or the mushroom emoji alone remains visible to avoid the brand overflowing beside the toggle button.
  • All card grids display as a single column. Species cards, article cards, and category cards stack vertically, each occupying 100% of the container width.
  • The dosage calculator form takes up the full viewport width with generous padding; number inputs are at least 48 px tall to prevent iOS zoom-on-focus (iOS zooms into inputs with font-size below 16 px).
  • Data tables — including species comparison tables — are wrapped in a horizontally scrollable <div> with overflow-x: auto. The table itself does not shrink columns to unreadable widths.
  • Heading sizes use clamp(): h1 is roughly 1.6 rem to avoid breaking across too many lines.
  • Touch targets for all interactive elements are padded to at least 44 × 44 px.

480 px — Large Phones (iPhone Plus models, large Android)

A modest but useful widening. At 480 px:

  • Navigation remains hamburger-style, but the brand area gains breathing room.
  • Hero section gains a slightly larger heading and the call-to-action buttons may sit side-by-side in a row rather than stacked vertically.
  • Article cards in category listings may shift to a 2-column grid using grid-template-columns: repeat(2, 1fr).
  • The dosage calculator form may show two fields per row where semantically appropriate (e.g., body weight value + unit selector).
  • Typography: body line-length starts to become a concern; a max-width on the article container begins to apply.

768 px — Tablets Portrait (iPad, Android tablets)

The first major layout shift. At 768 px:

  • The navigation bar transitions from hamburger to a horizontal inline link list. The toggle button is hidden. All nav links are visible simultaneously.
  • Species cards shift to a 3-column grid.
  • The page layout may introduce a sidebar pattern: main article content on the left (roughly 65% width) and a sticky table of contents or related-links sidebar on the right.
  • Species comparison tables display without horizontal scroll if the column count is moderate (up to 5–6 columns fit comfortably at 768 px with appropriate font sizing).
  • The page header section gets more vertical padding and the hero illustration (if present) appears beside the heading rather than below it.
  • Heading hierarchy: h1 reaches its "full" size via clamp() upper bound around 2.4–2.8 rem.

1024 px — Tablets Landscape / Small Desktop

At 1024 px:

  • Species grid shifts to 4 columns if cards are compact.
  • The sidebar, if used, gains more width; the main content area reaches its comfortable reading width (roughly 65–70 characters per line).
  • Navigation may add hover-reveal dropdown submenus for deeper category navigation.
  • Article images can float left or right within flowing text using float or grid placement, rather than always being full-width above the text.
  • The dosage calculator may render in a two-panel layout: inputs on the left, results summary on the right, side by side.

1200 px — Desktop

At 1200 px and above:

  • The container hits its maximum width (typically 1200–1280 px) and centers with auto margins. No further layout changes occur beyond this point.
  • Generous whitespace (padding, line-height, section margins) creates the "comfortable desktop reading" feel.
  • Full species comparison tables with 8–10 columns display without scroll on most desktop monitors.
  • Large header images or hero artwork display at full resolution with srcset serving a high-res variant.

3. Testing Methodologies

Chrome DevTools Device Emulation

Chrome DevTools is the first line of testing and by far the fastest iteration tool. To use it correctly:

  1. Open DevTools (F12 or Ctrl+Shift+I) and click the device-toggle icon (Ctrl+Shift+M on Windows/Linux, Cmd+Shift+M on Mac).
  2. Select a device from the preset dropdown (e.g., iPhone SE, Pixel 5, iPad Air) or type a custom width and height into the dimension fields.
  3. Set the device pixel ratio (DPR) to match real hardware — most modern phones use 2× or 3×. This matters for image sharpness testing: a 1× screenshot at 375 px looks the same as a 1× desktop, but a 3× DPR screen will expose low-resolution images.
  4. Enable "Responsive" mode and drag the handle to test every pixel width from 320 to 1400 to catch reflow errors at non-standard widths.
  5. Throttle the CPU to 4× slowdown and the network to "Fast 3G" to simulate mid-range phone performance. Check for layout shifts (CLS) and long-running paint operations.
  6. Use the "Sensors" panel to emulate touch events and test touch-only interactions like swipe gestures in the hamburger menu.

Limitations: DevTools emulation is not a real device. It does not replicate iOS Safari's rendering engine (Chrome uses Blink on desktop; the mobile emulator does not switch to WebKit). It cannot test actual touch latency, physical viewport height changes when the browser address bar hides on scroll, or hardware-accelerated compositing behaviors. Never ship based solely on DevTools approval.

Firefox Responsive Design Mode

Firefox's responsive mode (Ctrl+Shift+M) provides an independent second opinion. Firefox uses the Gecko engine, which interprets certain CSS — particularly viewport units and scroll behavior — differently from Chrome. Key differences to check: 100dvh support, scrollbar-gutter, and certain Flexbox edge cases. Firefox's built-in accessibility panel also flags color contrast issues and missing ARIA labels, making it a useful combined functional + accessibility audit tool.

BrowserStack for Real Device Testing

BrowserStack provides live interactive access to real physical devices. Priority test matrix for Psilobase:

  • iOS Safari: iPhone SE (375 px), iPhone 14 Pro (393 px), iPhone 14 Pro Max (430 px) — at minimum iOS 15, 16, 17.
  • Android Chrome: Pixel 7 (412 px), Samsung Galaxy S23 (360 px), a mid-range device like Redmi Note 12 (393 px).
  • iOS Chrome: Remember Chrome on iOS still uses WebKit, not Blink, so iOS Chrome ≠ desktop Chrome emulated.
  • Samsung Internet: Has its own rendering quirks, especially around text inflation and custom UI overlays.
  • Tablets: iPad (768 px portrait, 1024 px landscape), iPad Pro 12.9-inch (1024 px portrait).

Physical Device Testing Priorities

If physical devices are available, prioritize: one small-screen iOS device (iPhone SE or older model), one current iOS device (iPhone 13 or newer with notch/Dynamic Island), one mid-range Android device. Physical testing reveals: true touch target sizes relative to an actual thumb, how the browser chrome (address bar, bottom navigation bar) affects available viewport height, and real-world scroll inertia behavior.

4. Common Responsive Issues in Psychedelic and Harm-Reduction Portals

Sites in this niche have a predictable set of layout challenges that differ from general-purpose editorial sites, largely because of content density: long species names, complex comparison tables, dosage calculators, and detailed protocol steps. The following are the most common issues found during responsive audits of Psilobase.

Navigation Overflow at 320 px

The full navigation of Psilobase includes links to Species, Microdosing, Safety, Growing, FAQ, and potentially Research and Resources. At 320 px, even five links side-by-side will overflow. The fix is a CSS-triggered hamburger that hides all .nav-menu items and shows a toggle button. The breakpoint at which the full menu becomes visible again must be chosen carefully — if the menu triggers at 768 px but some tablets in portrait mode are 600 px, there is a gap. Solution: trigger the full inline menu at the point where all items fit without wrapping, typically around 640–700 px for a standard 5-item menu, or use flex-wrap with a gap and let the items wrap to a second row at intermediate widths.

Species Comparison Tables Needing Horizontal Scroll

The species comparison section may include a table with columns for scientific name, common name, potency level, active compounds, typical dose range, visual identification, legal status, and habitat. That is 8 columns. At 320 px, no table with 8 columns is readable without horizontal scroll. The correct pattern is:

  • Wrap the table in a <div role="region" aria-label="Species comparison" tabindex="0"> with overflow-x: auto. The tabindex="0" makes the scroll region keyboard-accessible so keyboard users can scroll it.
  • Add a visual cue (e.g., a faint "scroll right" indicator or a CSS-generated arrow) so touch users know to swipe.
  • Set minimum column widths so content is not crushed: th, td { min-width: 80px; white-space: nowrap; } for short values, with an exception for descriptive columns which should allow wrapping.
  • At 768 px and above, consider whether the table can drop horizontal scroll entirely or still benefits from it for very wide tables.

Dosage Calculator Forms Cramping on Mobile

The Psilobase dosage calculator collects body weight, experience level, set and setting context, and mushroom variety. On small screens, stacking all of these inputs vertically without grouping creates an overwhelming single-column form that appears to scroll forever. Mitigations:

  • Group related inputs under visible fieldset/legend pairs with a small "step" indicator.
  • Use a progressive-disclosure pattern: show the most critical inputs first (weight, experience level), then reveal secondary inputs after the first group is complete.
  • Ensure all <input type="number"> elements have font-size: 16px or larger in the stylesheet to prevent iOS Safari from auto-zooming the viewport on focus.
  • Add inputmode="decimal" to weight inputs so Android shows the numeric keypad with a decimal point rather than the full alphabetic keyboard.

Long Species Names Wrapping Awkwardly

Scientific names such as Psilocybe zapotecorum, Psilocybe hoogshagenii, or Gymnopilus junonius are long strings that do not have natural word-break points at the expected places. Without intervention, they force the card to widen (breaking grid layout) or break at a random character (creating visual ugliness). The fix is adding overflow-wrap: break-word and hyphens: auto with an appropriate lang="la" attribute on the <em> element (since these are Latin names, the hyphenation algorithm for Latin produces better breaks than the default English algorithm).

5. Testing Interactive Tools on Mobile

Dosage Calculator: Number Inputs and Sliders

The dosage calculator is the most complex interactive element on Psilobase. Key mobile-specific issues to test:

  • iOS viewport zoom on focus. Any input with a CSS font-size below 16 px causes iOS Safari to zoom the entire viewport when the input is focused. This is disorienting and breaks the layout. Fix: ensure all inputs, selects, and textareas have font-size: 16px or use font-size: 1rem with a root font-size of at least 16 px.
  • Range slider width. An <input type="range"> should span the full width of its container. Test that the thumb (drag handle) is large enough to tap accurately on mobile — browsers render the default thumb at around 20 px on iOS, which is below the recommended 44 px tap target. Custom CSS (-webkit-slider-thumb and ::-moz-range-thumb) can increase thumb size.
  • Android virtual keyboard covering fields. When a keyboard opens on Android, it resizes the visual viewport but not the layout viewport. A fixed-position "calculate" button at the bottom of the screen can become hidden behind the keyboard. Fix: avoid position:fixed for CTAs within form containers; let them flow in the document and scroll to them.
  • Numeric keyboard type. Use inputmode="decimal" for weight and dose inputs. Use type="number" only where spinners are acceptable, since type="number" adds up/down spinner buttons that are nearly impossible to tap accurately on mobile.

Protocol Selector: Radio Groups and Dropdowns

The protocol selector lets users choose between microdose, low, medium, and high-dose protocols using radio buttons or a segmented control. Mobile considerations:

  • Radio button labels must have large tap areas. The default radio button touch target is tiny. Use CSS to make the entire label (not just the circle) tappable: label { display: block; padding: 12px 16px; cursor: pointer; }.
  • Dropdowns (<select>) behave differently on iOS and Android. iOS renders them as a bottom-sheet picker wheel; Android renders them as a popup list. Both are native and accessible, but the visual styling cannot be fully customized on iOS. Test that the selected option is clearly legible in the button-like appearance that iOS applies to a <select>.
  • Custom dropdown replacements (built with role="listbox" and role="option") require careful ARIA implementation and focus management. If using a custom dropdown, ensure it closes on outside click, closes on Escape, and returns focus to the trigger when closed.

Integration Journal Forms

The integration journal allows users to log reflections and notes after an experience. This involves <textarea> elements. Mobile issues:

  • Textarea resize handle. By default, textareas show a resize handle in the bottom-right corner. On mobile this is not useful and can cause accidental resizing. Set resize: vertical or resize: none on mobile via a media query.
  • Autogrow behavior. A textarea that autogrows as the user types (via a JavaScript listener setting element.style.height) is more comfortable on mobile than fixed-height textareas that require internal scrolling, which conflicts with the page scroll.
  • Scroll restoration. After a form submission or AJAX save, if the page reloads, scroll position should be restored or the user should be scrolled to a success message. On mobile, being deposited at the top of a long form page after saving a journal entry is a poor UX.

6. iOS Safari vs. Android Chrome Differences

Safari on iOS and Chrome on Android are the two dominant mobile browsers, together accounting for over 90% of mobile web traffic globally. They diverge in important ways that affect the Psilobase layout.

Viewport Unit Bugs — 100vh

The classic mobile viewport problem: on both iOS Safari and older Chrome on Android, 100vh equals the viewport height including the browser chrome (address bar, tab bar). When a user scrolls down and the browser chrome hides to give more reading space, the "real" available height is larger than 100vh — but elements sized to 100vh do not re-measure. This causes hero sections sized to height: 100vh to visually overflow at the bottom on initial load, then appear correct after scroll.

Fix: Use height: 100dvh (dynamic viewport height), which is supported in all modern browsers since 2022–2023. It updates as the browser chrome shows/hides. For older browser fallback: height: 100vh; height: 100dvh;. Alternatively, avoid full-viewport hero sections and use percentage-based or content-driven heights that do not depend on the viewport.

Safe Area Insets for Notched Phones

iPhones from the X onward (and modern notched Android devices) have physical notches or Dynamic Islands at the top and home-indicator bars at the bottom. The CSS environment variables env(safe-area-inset-top), env(safe-area-inset-bottom), env(safe-area-inset-left), env(safe-area-inset-right) provide the pixel offsets needed to prevent content from being hidden behind these elements. For Psilobase:

  • The fixed/sticky navigation bar needs padding-top: env(safe-area-inset-top) if it sits at the very top of the screen in full-screen mode.
  • A fixed bottom-of-screen CTA (e.g., "Start Dosage Calculator") needs padding-bottom: env(safe-area-inset-bottom) to clear the home indicator.
  • Requires the viewport meta tag to include viewport-fit=cover: <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">. Without this, the browser applies its own padding and the environment variables return 0.

Scroll Behavior

iOS Safari uses a momentum-based scroll that continues after the user lifts their finger. This is smooth but interacts poorly with overflow: hidden set on the <body> when a modal or mobile menu is open — on iOS, the page behind the overlay can still scroll (the infamous iOS scroll-lock bug). Fix: when locking scroll for an open mobile menu, set position: fixed; width: 100%; top: -[scrollY]px on the body, then restore and scroll back on close. Android Chrome handles overflow: hidden on the body correctly without this workaround.

input[type=number] Spinners

On desktop browsers, input[type=number] shows increment/decrement spinner arrows. On iOS Safari, these spinners do not appear, so the input looks like a plain text input. On some Android browsers, they appear as tiny plus/minus buttons. The safest approach for mobile: use type="text" with inputmode="decimal" and JavaScript validation to accept only numeric input. This gives the numeric keyboard on both platforms without the layout-breaking spinner arrows.

Touch Event Handling

iOS has a 300 ms click delay on elements that are not explicitly touch-optimized (a legacy workaround for double-tap-to-zoom). This is resolved by adding touch-action: manipulation to buttons and links, which tells the browser not to handle double-tap-to-zoom on those elements, eliminating the delay. Android Chrome removed the delay years ago for pages with a proper viewport meta tag, but the CSS property is harmless to include for both platforms.

7. Hamburger Menu Behavior

The mobile navigation toggle is one of the most scrutinized patterns in responsive design. Getting it right for Psilobase means handling keyboard accessibility, focus management, screen reader announcements, and smooth animation without causing layout thrash or accessibility failures.

CSS-Only vs. JavaScript Toggle

A purely CSS hamburger (using a hidden checkbox hack or :focus-within on the nav) works without JavaScript but has accessibility limitations — it cannot update aria-expanded, cannot trap focus, and cannot close on Escape. For Psilobase, a JavaScript-enhanced toggle is the correct approach, with the CSS providing the visual default (menu hidden, button visible) and JavaScript adding the functional layer.

ARIA expanded Attribute

The toggle button must carry aria-expanded="false" in the closed state and aria-expanded="true" in the open state. Screen readers announce "navigation, collapsed button" vs. "navigation, expanded button," giving users without sight the context they need. The button should also have aria-controls="nav-menu" pointing to the id of the navigation list, and aria-label="Open navigation menu" (or "Close navigation menu" toggled by JavaScript) since the icon-only button has no visible text label.

Focus Trap in Open Menu

When the mobile menu opens and occupies the full screen (or a large overlay), keyboard focus must be trapped within the menu. If the user tabs past the last nav link, focus should cycle back to the first nav link (or to the close button). Without a focus trap, a keyboard user pressing Tab would silently focus invisible elements behind the overlay. Implementation: listen for keydown on the menu container; on Tab (forward) from the last focusable element, redirect focus to the first; on Shift+Tab from the first, redirect to the last.

Close on Outside Click

Touch users expect to close the menu by tapping outside it. Implement with a document-level click listener that checks if the click target is inside the menu or the toggle button; if not, close the menu. Be careful not to close the menu when the user taps a link inside the menu — the navigation itself should close the menu (or the link will navigate away, which has the same effect).

Animation Performance — transform not top/left

Animating the menu opening/closing should use CSS transform: translateX() or transform: translateY() rather than animating left, right, top, or height properties. The reason: transform and opacity are the only CSS properties that modern browsers can animate on the GPU compositor thread without triggering layout or paint. Animating height: 0 to height: auto (a common pattern) causes layout recalculation on every frame, which is visible as jank especially on mid-range phones. The modern approach: animate transform: translateX(-100%) (slide in from left) or use the Web Animations API for JavaScript-driven animation.

Keyboard Escape to Close

A listener on the keydown event for key === 'Escape' should close the menu and return focus to the toggle button. This is a WCAG 2.1 requirement (Success Criterion 1.4.13 for content on hover/focus, and general good practice for all dialogs and overlays). Without it, keyboard users who accidentally open the mobile menu have no way to close it without using the pointer.

8. Card Layouts at Different Widths

Psilobase uses cards extensively: species cards in the species index, article cards in the blog and research sections, and category cards on the homepage. Each card type has different content density and therefore different optimal column counts at each breakpoint.

Species Cards — 1 → 2 → 3 → 4 Columns

Species cards typically contain: a thumbnail photograph, common name, scientific name (in italics), a potency badge, and a short one-line habitat description. They are visually rich but moderately dense in text. The recommended column progression:

  • 320–479 px: 1 column. Each card fills the full container width (~300 px). The image is full width, the text below it.
  • 480–767 px: 2 columns using grid-template-columns: repeat(2, 1fr). Cards are roughly 220 px wide — enough for the thumbnail and the short names without wrapping issues.
  • 768–1023 px: 3 columns. Cards at ~220–230 px. Scientific names may need overflow-wrap: break-word; hyphens: auto; at this width.
  • 1024 px+: 4 columns. Cards at ~260 px on a 1200 px container. The thumbnail is tall enough to show the distinctive features of each species clearly.

CSS Grid's auto-fill and minmax() provide a cleaner implementation than manual breakpoints for this kind of content: grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)). This naturally flows from 1 column at 320 px to 4+ columns on wide desktops, with no media queries needed for the column count change — only for the gap and padding adjustments.

Article and Category Cards

Article cards (research summaries, harm-reduction guides) contain more text: a title (up to 70 characters), a 2-line excerpt, a category tag, and a date. These need slightly wider minimum widths to remain readable. A minmax(280px, 1fr) grid gives: 1 column at 320 px, 2 columns from ~580 px, 3 columns from ~880 px. Category cards (homepage navigation tiles) are simpler (icon + label) and can tolerate narrower widths — minmax(140px, 1fr) allows 2 columns from 320 px and up to 6 columns on wide desktop.

Flexbox vs. CSS Grid for Card Layouts

CSS Grid is preferable for card grids because it enforces a strict 2D layout: all cards in a row share the same height, and grid lines keep columns aligned even when card content lengths vary. Flexbox is appropriate for single-axis card rows (e.g., a horizontal scrolling "featured species" strip) where items should size to their content rather than conforming to a grid column. The key difference: Grid controls both rows and columns simultaneously; Flexbox is inherently one-directional.

9. Typography Scaling

Base Font Size — 16 px Minimum

The base body font size must be at least 16 px (1 rem with a 16 px root). Anything smaller causes readability problems in low-light conditions (common for late-night users of harm-reduction content) and triggers the iOS Safari auto-zoom bug in form inputs. A common mistake is setting font-size: 14px on the <body> to fit more content — avoid this entirely. The solution when content feels too dense is to reduce padding and margin, not font size.

Fluid Type with clamp()

Rather than stepping font sizes at discrete breakpoints (which creates sudden jumps), fluid typography with CSS clamp() scales smoothly between a minimum at 320 px and a maximum at 1200 px. The pattern:

h1 { font-size: clamp(1.6rem, 4vw + 0.5rem, 2.8rem); }
h2 { font-size: clamp(1.3rem, 3vw + 0.4rem, 2.0rem); }
h3 { font-size: clamp(1.1rem, 2vw + 0.3rem, 1.5rem); }
body { font-size: clamp(1rem, 1.5vw + 0.5rem, 1.125rem); }

The clamp(min, preferred, max) function uses the preferred value (a viewport-relative calculation) but clamps it between the minimum and maximum, ensuring legibility at both extremes without abrupt jumps.

Heading Hierarchy on Mobile

On small screens, an h1 set to 2.8 rem may be fine on desktop but push the heading to 4 or 5 lines on a 320 px screen, burying the lead content. The clamp() approach handles this automatically. However, also review the heading text length: headings should be concise enough to fit 2 lines maximum on a 320 px screen at the minimum font size. Species page headings like "Psilocybe cubensis: The Most Commonly Cultivated Psilocybin Mushroom" need to either be shortened on mobile (using a shorter version in the page header section) or accepted as multi-line headings with appropriate line-height.

Line Length — 45 to 75 Characters Optimal

Optimal reading line length is 45–75 characters (roughly 8–15 words per line). On a 320 px screen with 16 px type and 16 px horizontal padding on each side, the readable area is 288 px — which accommodates roughly 45–50 characters at a normal character width. This is slightly short of optimal but acceptable. On tablets at 768 px, an unconstrained full-width text block would be ~720 px — far too wide (~100+ characters). Apply a maximum width on article containers: max-width: 68ch centers text within its container and applies a character-count-based constraint that is independent of font size.

Paragraph Spacing

Paragraph spacing (the gap between paragraphs) should scale with the base font size using em units: p + p { margin-top: 1.5em; }. On mobile, slightly increased line-height (1.6–1.7 versus 1.5 on desktop) compensates for the shorter line lengths and helps the eye track back to the start of the next line. Do not reduce line-height on mobile to "save space" — it is a major readability degradation.

10. Image Responsiveness

srcset with Multiple Sizes

Every content image on Psilobase should be served at multiple resolutions using the srcset attribute. A typical pattern for a species card thumbnail:

<img
  src="/images/species/cubensis-400.jpg"
  srcset="/images/species/cubensis-400.jpg 400w,
          /images/species/cubensis-800.jpg 800w,
          /images/species/cubensis-1200.jpg 1200w"
  sizes="(max-width: 480px) 100vw,
         (max-width: 768px) 50vw,
         (max-width: 1024px) 33vw,
         25vw"
  alt="Psilocybe cubensis growing on substrate"
  loading="lazy"
  decoding="async"
  width="400"
  height="300"
>

The sizes attribute tells the browser what CSS width the image will occupy at each viewport width, allowing it to choose the smallest srcset candidate that is still sharp enough. On a 320 px screen, the browser selects the 400 w image; on a 1200 px desktop with 3× DPR (e.g., iPhone 14 Pro), it may select the 1200 w image even for a 400 px physical column.

picture Element with WebP + JPEG Fallback

For improved compression, serve WebP where supported and fall back to JPEG for older browsers (Safari before iOS 14, some older Android browsers):

<picture>
  <source
    type="image/webp"
    srcset="/images/species/cubensis-400.webp 400w,
            /images/species/cubensis-800.webp 800w"
    sizes="(max-width: 768px) 100vw, 50vw"
  >
  <img
    src="/images/species/cubensis-400.jpg"
    srcset="/images/species/cubensis-400.jpg 400w,
            /images/species/cubensis-800.jpg 800w"
    sizes="(max-width: 768px) 100vw, 50vw"
    alt="Psilocybe cubensis"
    loading="lazy"
    width="400" height="300"
  >
</picture>

WebP typically achieves 25–35% smaller file sizes than JPEG at equivalent visual quality, which meaningfully improves load time for mobile users on slower connections — an important concern for a portal accessed under urgent circumstances.

object-fit for Card Thumbnails

In a multi-column grid, all card thumbnails need to share the same height for a uniform layout. Without intervention, a portrait-orientation mushroom photo will render taller than a landscape one, breaking the grid alignment. Fix: set a fixed height on the image container and use object-fit: cover on the image to crop and fill the container without distorting the aspect ratio. The focal point (the actual mushroom cap) should be positioned at the top-center using object-position: center top so the most visually distinctive part of the photograph is preserved in the crop.

Mushroom Photography Aspect Ratios

Field photography of mushrooms is typically in landscape (3:2 or 4:3) or portrait (2:3) depending on whether the shot shows the cap from above or the full fruiting body from the side. Psilobase should standardize card thumbnails to a consistent aspect ratio — 4:3 or 16:9 are common choices. The aspect-ratio CSS property (aspect-ratio: 4 / 3) on the image container prevents layout shift during lazy-loading by reserving the correct space before the image loads, without needing explicit width and height attributes on the element itself (though those should be included too for browsers that do not yet support aspect-ratio).

11. Comprehensive Responsive Testing Checklist

Use the following table as a structured audit record. Testers should record the actual device or emulated width in the Viewport column, test each element as described, compare against the expected behavior, and mark Pass or Fail. Failed items should be filed as issues with a screenshot attached.

Psilobase Responsive Design Testing Checklist — run at each major breakpoint
Viewport Element / Feature Expected Behavior Pass / Fail Notes
320 px Navigation bar Hamburger button visible; nav-menu hidden; logo legible; no overflow
320 px Hamburger toggle (tap) Menu opens with animation; all nav links visible and tappable; aria-expanded="true"
320 px Hamburger toggle (keyboard) Enter/Space opens menu; Escape closes; focus returns to toggle on close
320 px Homepage hero section Heading fits in 3 lines max; CTA button full-width and tappable (min 44px height)
320 px Species card grid 1 column; cards full width; thumbnail not distorted; scientific names wrapped with hyphens
320 px Species comparison table Horizontal scroll active; table not cutting content; scroll hint visible
320 px Dosage calculator form All inputs full-width; font-size ≥ 16px on all inputs; no iOS zoom on focus
320 px Body text line length ~45 chars per line; no horizontal overflow; no content clipped
320 px Images in articles max-width: 100%; height auto; no overflow beyond container
320 px Skip link Visible on focus; jumps to #main-content
320 px Footer Stacked single column; all links tappable; no overflow
480 px Navigation bar Still hamburger; brand area has more room; no overlap
480 px Article card grid 2-column grid; cards equal height; excerpts not truncated awkwardly
480 px Hero CTA buttons Two buttons side by side (if applicable) without wrapping
480 px Dosage calculator sliders Slider thumb large enough to tap (≥ 44px touch area); value label legible
768 px Navigation bar Full inline nav menu visible; hamburger button hidden; all 5 links fit in one row
768 px Species card grid 3-column grid; card images sharp; potency badges align across columns
768 px Article sidebar Table of contents visible beside article; not overlapping content
768 px Species comparison table 5–6 columns display without horizontal scroll; text not truncated
768 px Body text line length max-width container applied; ~65 chars per line in article body
768 px Protocol selector radio group Radio options in a 2×2 grid or 4-in-a-row; labels readable; tap areas ≥ 44px
768 px Integration journal textarea Full-width; resize handle present but optional on tablet; no resize=none cutting off text
1024 px Navigation bar Full nav visible; optional dropdown submenus appear on hover without layout shift
1024 px Species card grid 4 columns; images sharp (check WebP delivery); object-fit: cover maintains crop
1024 px Dosage calculator layout Two-panel: inputs left, results right; no orphaned labels
1024 px Article images Float left/right possible; text wraps correctly; no text-image overlap
1200 px Container max-width Content does not stretch beyond ~1200–1280px; centered with auto margins
1200 px Full comparison table 8-column table displays without scroll on 1920px monitor; all columns legible
1200 px Typography Body text at 1.125 rem (18px); line-height ~1.6; heading sizes at clamp() maximum
All Horizontal scroll on body No horizontal scrollbar appears at any viewport width
All Touch targets All interactive elements ≥ 44 × 44 px (WCAG 2.5.5)
All Color contrast Body text ≥ 4.5:1 ratio; large text ≥ 3:1 ratio (WCAG 1.4.3)
All prefers-reduced-motion Hamburger animation, card hover transitions, scroll animations all disabled
All 200% browser zoom Content reflows; no horizontal overflow; all text readable; no overlap
iOS Safari 100vh hero section Hero not taller than visible viewport; no content below the fold on first render
iOS Safari Input zoom No viewport zoom on focus of any input; font-size ≥ 16px confirmed
iOS Safari Safe area insets Fixed/sticky nav clears notch; fixed bottom CTAs clear home indicator
iOS Safari Modal/menu scroll lock Background page does not scroll when mobile menu is open
Android Chrome Keyboard covering inputs Focused input scrolls into view above keyboard; submit button remains reachable
Android Chrome touch-action: manipulation No 300ms click delay on nav links and form buttons

Frequently Asked Questions — Responsive Design Testing

Why does responsive design matter specifically for a harm-reduction portal like Psilobase?

Harm-reduction portals serve users who may be under unusual stress or urgency at the moment of access. A person seeking dosage reference information, contraindication warnings, or grounding techniques during or immediately before a psilocybin experience is very likely to be using a smartphone, possibly in low-light conditions, possibly with reduced fine motor control. A broken mobile layout — a menu that will not open, a table that overflows its container, text that requires pinch-zooming — is not just a usability inconvenience but a potential barrier to accessing safety-critical information. This is why Psilobase treats mobile-first design as a harm-reduction commitment, not a technical nicety.

What is the single most common responsive failure across all breakpoints?

Horizontal overflow — content that causes the body to be wider than the viewport, requiring the user to scroll sideways — is by far the most common responsive failure. It is typically caused by one of three things: an image without max-width: 100%, a table without a scrollable wrapper, or a long unbreakable string (a URL, a scientific name, a code snippet) without overflow-wrap: break-word. A quick diagnostic: open browser DevTools, go to the Console, and run document.querySelectorAll('*') filtered by elements with offsetWidth > document.body.offsetWidth. This identifies the overflowing element immediately without manual bisection.

How should the Psilobase design team prioritize which devices to test on?

Prioritize based on the actual device distribution from site analytics. However, as a general rule for a UK and US-oriented English-language site, start with: iPhone SE (375 px, iOS Safari — most restrictive small-screen Apple device), iPhone 14 or 15 (393 px, iOS Safari — current mainstream Apple device), a mid-range Android such as Pixel 6a or Samsung Galaxy A-series (360–412 px, Chrome), and an iPad (768 px portrait, iPadOS Safari). Desktop Chrome and Firefox at 1200 px and 1440 px complete the critical matrix. Budget Android devices on slow networks are a secondary priority but should be tested at least monthly using BrowserStack's budget device catalog.

What does iOS Safari's 100vh bug actually look like in practice, and how do I confirm it is fixed?

On a real iPhone or in a Chrome DevTools iOS Safari simulation, set a div to height: 100vh and load the page. You will notice the div extends slightly below the visible screen on initial load. When you scroll, the browser chrome (address bar + tab bar) collapses, the visible viewport grows, and the div no longer fills the screen — it is now shorter than the visible area. This is the manifestation: 100vh was calculated at initial load including the browser chrome, and it is not recalculated when the chrome retracts. To confirm the fix with 100dvh: the div should always precisely fill the visible viewport regardless of scroll position or browser chrome visibility. Test by setting a bright red background on the div — if you see any page background color at the bottom on initial load, the bug is present.

How can I test the focus trap in the mobile navigation menu?

Connect a keyboard (physical keyboard or Tab key in DevTools device emulation mode). Open the mobile menu using the Enter key on the hamburger button. Then repeatedly press Tab to cycle through the nav links. The focus should cycle back to the first link (or the close button) after reaching the last link, without ever escaping the menu. Then test Shift+Tab from the first link — focus should jump to the last link, not leave the menu. Finally, press Escape — the menu should close and focus should return to the hamburger button. If focus at any point disappears into the page behind the overlay (visible as the focus indicator jumping to the main article or footer), the focus trap is broken. Screen readers (NVDA + Firefox, VoiceOver + Safari) should also be tested to verify the menu is announced correctly when opening and closing.

What is the correct way to prevent iOS viewport zoom on form inputs?

iOS Safari automatically zooms in on form inputs when the input's computed font-size is below 16 px. The fix is straightforward: ensure every input, select, and textarea element has font-size: 16px or larger in the stylesheet. This applies to the element itself, not a parent container — CSS inheritance does not always carry the computed value if any intermediate selector reduces it. A safe global rule is: input, select, textarea { font-size: max(16px, 1rem); }. Note: adding user-scalable=no to the viewport meta tag would also prevent zoom, but this is an accessibility violation (WCAG 1.4.4 Resize Text) and should never be used. The font-size fix is the correct approach.

What is the difference between CSS Grid auto-fill and auto-fit for the species card layout?

auto-fill and auto-fit both work with repeat() and minmax() to create a flexible number of grid columns. The difference appears when there are fewer items than the number of columns that would fit at the current viewport width. With auto-fill, empty grid tracks are created and the existing items do not expand beyond their minimum width. With auto-fit, empty tracks are collapsed and the existing items stretch to fill the available space. For species cards, auto-fill is generally preferable: if there are only 3 species in a new subcategory, they should display at the same width as in a full category page, not stretch to fill the entire container. auto-fit is better for sections where you always want items to fill the row regardless of count, such as a "Top 3 species" feature strip.

How should data tables in species comparisons handle very narrow viewports?

There are three valid approaches, each with trade-offs. The first is horizontal scrolling: wrap the table in a container with overflow-x: auto. The table remains in its natural tabular form and is accessible to screen readers and keyboard users; the user simply scrolls sideways. This is the simplest and most semantically correct approach. The second approach is a "card transform" at mobile widths: use CSS to hide the table headers and display each row as a card, with data attributes used as pseudo-labels. This looks beautiful on mobile but is complex to implement accessibly (the relationship between header and data cell must be communicated to screen readers differently). The third approach is column prioritization: hide lower-priority columns at narrow widths with a "Show more" toggle. For Psilobase's harm-reduction context, horizontal scroll is recommended as the primary approach — it is the most robust and accessible, requires no JavaScript, and preserves all data at all times. Add a visible scroll hint for discoverability.

Should the hamburger menu use a CSS-only or JavaScript toggle on Psilobase?

A JavaScript-enhanced toggle is correct for Psilobase. Pure CSS solutions (the checkbox hack, :focus-within) cannot update aria-expanded on the toggle button, cannot trap focus within the open menu, and cannot close the menu on Escape. These are not optional enhancements — they are WCAG-required behaviors for any interactive disclosure widget. JavaScript should be loaded via a defer attribute so it does not block rendering, and the initial HTML should have aria-expanded="false" on the button. If JavaScript fails to load for any reason, the menu button is still visible and focusable, and while the menu will not open, the page content is still fully accessible via the normal page scroll. This is acceptable graceful degradation.

How do I verify that WebP images are actually being served to compatible browsers?

Open Chrome DevTools, go to the Network tab, and filter by "Img". Reload the page. Click on any image request and look at the "Response Headers" section. The content-type header should show image/webp for WebP images. Then do the same in Firefox — it should also request and receive WebP (Firefox has supported WebP since version 65). To confirm the JPEG fallback: open Safari 13 (macOS Mojave, available via BrowserStack) — this version predates WebP support, and the <picture> element should cause the browser to load the <img> JPEG fallback instead of the <source type="image/webp"> variant. Verify the content-type in Network logs shows image/jpeg. This three-step check confirms the <picture> element is working correctly for both modern and legacy browsers.