LAN file and folder sharing tool. The desktop app starts a share; guests scan a QR code or enter an address in a desktop or mobile browser. This file is an implementation handoff document. All values, colors, and component behavior come from the current design. Do not change them by feel.
Warm cream background + brick-red accent, serif titles, and monospace technical values such as IP addresses, ports, paths, sizes, and dates. Restrained, quiet, and ticket-like. Use medium radii, light shadows, and generous spacing. Do not use gradient backgrounds, emoji, or colorful glow effects.
All colors are generated by getTokens(t) and must be implemented as theme variables with light and dark mode support. The accent color can be user-selected.
Default: #df4f28 (brick red-orange). Available options: #df4f28 / #2a6fdb / #1f8a5b / #7a5ae0.
accentSoft: light modergba(223,79,40,0.12), dark modergba(223,79,40,0.18). Note:accentSoftshould follow the selected accent hue at 12% / 18% opacity.
| Token | Light | Dark | Purpose |
|---|---|---|---|
bg |
#efeae1 |
#1b1814 |
Page / window background |
surface |
#fdfbf7 |
#262219 |
Cards / inputs / ticket front |
surfaceAlt |
#f5f0e7 |
#211e16 |
Hover state and secondary fills |
field |
#efe9de |
#1d1a14 |
Address bar / port input background |
ink |
#2a261d |
#f2eee5 |
Primary text |
inkMute |
#8c8475 |
#a59d8c |
Secondary text and helper copy |
inkFaint |
#b4ab99 |
#6d675a |
Weakest text / placeholders / dates |
line |
#e7dfd1 |
#37322a |
Default borders and dividers |
lineStrong |
#dacfbd |
#494238 |
Strong borders / dashed lines / focus |
ok |
#2f9e57 |
#2f9e57 |
Success: port available, copy succeeded |
danger |
#c43c1c |
#ef8a6e |
Danger / stop / invalid port |
warn |
#b67708 |
#e0a83a |
Warning / occupied port |
The design canvas neutral background is #f0eee9, very close to bg.
Use three font families with strict roles:
displayfor titles:"Source Serif 4", Georgia, serif, weight 600. Users can switch titles to sans; whentitleSerif=false,displayfalls back to sans.sansfor body and controls:-apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, "PingFang SC", sans-serif.monofor technical values:"JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace. All IP addresses, ports, URLs, paths, file sizes, item counts, and dates use mono.
| Role | Size | Weight | Family | Notes |
|---|---|---|---|---|
| LocalShare wordmark | 22-28 | 600 | display | Empty state 28, compact header 22 |
| Client page title, e.g. Downloads | 44 / 30 mobile | 600 | display | Slightly tight display letter spacing |
| Section eyebrow | 11-12 | 700 | sans | Accent color, modest letter spacing |
| Card / Settings title | 16-21 | 600-700 | display/sans | |
| Body / list item name | 13-16 | 500-600 | sans | |
| Helper / secondary text | 11.5-12.5 | 400-600 | sans | inkMute |
| Technical values | 11-14 | 500-700 | mono | Address, port, size, date |
- Radius: buttons 10-12; cards / tickets 14-18; inputs 8-12; pills / status dots
999; file and folder glyph squares usesize * 0.26~0.28. - Border: default
1px solid line; emphasis and dashed rules uselineStrong; ticket tear line uses1.5px dashed lineStrong. - Window padding: desktop app card
LSWindefaults to 20-24. - Shadows:
- Desktop card:
0 1px 2px rgba(0,0,0,.10), 0 18px 50px rgba(40,30,15,.16) - Browser window:
0 1px 2px rgba(0,0,0,.10), 0 20px 60px rgba(40,30,15,.18) - Ticket / boarding pass:
0 10px 30px rgba(40,30,15,0.10) - QR card:
0 8px 24px rgba(40,30,15,0.08) - Dropdown / popover:
0 12px 34px rgba(40,30,15,.20) - Primary button:
0 1px 2px rgba(0,0,0,.12); stop button resting shadow0 1px 2px rgba(0,0,0,.18), hover0 4px 14px <danger>59
- Desktop card:
Use custom 16x16 line icons with stroke=currentColor, strokeWidth 1.6-1.8, and rounded caps. No emoji and no colorful filled icons.
Existing icon names: copy / open / globe / wifi / file / swap / stop / play / chevron / qr / help / check / phone / search / sort / x.
New icons must keep the same visual weight and 16px grid.
| Component | Form | Height | Purpose |
|---|---|---|---|
| PrimaryBtn | Solid accent, white text, weight 650 | 44, usually full width | Primary positive action: choose files/folders, broadcast again |
| GhostBtn | Transparent + line border; hover uses surfaceAlt / lineStrong |
34 | Secondary actions: change, reshare, view all |
| DangerBtn | Solid danger, white text, weight 700 |
34 | Destructive action: stop sharing. Must be visually prominent; do not render as ghost |
- IconBtn: 34x34 square, radius 10, for gear/help icon-only entries.
- Button icons: 14-15px with
gap 6-7from text.
- StatusPill: radius 999,
surfacebackground +lineborder, containing a status dot, label, vertical divider, and:portin mono.- States:
broadcastinguses accent + pulsing dot;connectedshows the visitor line with device name;stoppedusesinkMutewithout pulse. - Visitor summaries: one visitor
<name> is browsing; multiple visitors<name> and N others are browsing; unknown names fall back to the IP suffix. Long text truncates at the end.
- States:
- Dot: status dot.
liveaddslsPulseexpansion animation,1.8s ease-out infinite. - Chip: radius 999 label. Accent state uses
accentSoftbackground + accent text; normal state uses transparent background +lineborder.
- Port input:
fieldbackground, mono, about 72px wide; mono prefix at left, e.g.192.168.31.223 :. - Search input:
surfacebackground, search icon at left ininkMute, circularxclear button when non-empty. - Switch: 40x24, radius 999. On = accent background with handle on the right.
lockedlowers opacity to 0.55 and disables interaction, used for "Read and download, always on".
- LSWin: borderless Mac utility window, 40px title bar with three traffic-light dots (
#ff5f57/#febc2e/#28c840) and a flex-column content area. Every desktop app screen is wrapped inLSWin. - Ticket card / VariantB:
surfacecard with radius 18. Top stub contains file metadata, middle tear rule uses side notches filled withbgplus a dashed line, bottom contains QR and address. - Bracket: L-shaped corner marks for client lists (
lineStrong, 2px), used as a viewfinder decoration.
- FolderGlyph: rounded square,
accentSoftbackground + accent folder shape. - FileGlyph / RowIcon: rounded square, color-coded by file type. Center shows extension in mono or a globe icon plus extension.
TYPE_TINT values: web #c4451f, spreadsheet #1f8a5b, image #2a6fdb, PDF #cf4444, Markdown #7a5ae0, document #a9772a. Background is the same color at 12-13% opacity. In dark mode, all type backgrounds use rgba(255,255,255,0.06).
- QR: decorative placeholder in mocks, deterministic pseudo QR, white background, rounded corners, foreground
#1a1813(dark mode#0e0c09). Replace with a real QR code in implementation, but keep size, radius, white background, and padding. - CopyPill:
fieldrounded bar. Left side is mono address with ellipsis; right side has copy button with green check for 1.3s after success and an open-in-browser button.
demoState has broadcasting / connected / stopped. It controls StatusPill, whether QR appears dimmed, and the bottom action button state: stop versus broadcast again.
Permission object: { read(always locked on), add, edit, del }. Use permSummary(perm) as the single derived source. Every sharing UI reads from it; do not hard-code labels in individual screens.
- If any write permission is enabled (
add/edit/del),writable=true. tag: read-only ->Read-only; writable ->Read/write.eyebrow:LAN · Read-only shareversusLAN · Read/write share.chips: read-only =[Read-only, Downloadable]; writable =[Downloadable, Uploadable?, Editable?, Deletable?]depending on enabled permissions.
Linked surfaces driven by the same permission state: empty-state eyebrow, folder-share card eyebrow and permission chips, single-file ticket "Sharing · read-only/read-write", Settings "Current: read-only/read-write" tag, bottom warning copy, and alternate VariantA/C eyebrows. Changing write permissions in Settings must update the wording at the top of other screens immediately.
Validate as the user types via validatePort:
- Empty ->
Enter a port number(error). <1024->Must be >= 1024 · lower ports are reserved by the system(error).>65535->Out of range · maximum port is 65535(error).- Occupied sample set (
80,443,3000,5000,5432,3306,8000,7890) ->Port X is already in use(warn) and show a Use :nextAvailablePort button that fills the next available port. - Otherwise ->
Port available(ok).
Visuals: input/container border follows status color (ok keeps default line; warn/error use semantic colors). Right-side badge shows Available / In use / Invalid with check/help icon. Helper line follows status color; ok copy says changing the port restarts the service and shared links need updating.
Toolbar sits below the title and above filter chips: search input on the left, sort dropdown on the right.
- Type filters:
All / Folders / Web / PDF / Images / Markdown / Tables / Documents. They must actually filter items; Folders means directories, the rest useitem.type. - Sort dropdown:
Default order / Name A-Z / Name Z-A / Newest first / Oldest first; selected item gets a check. Folders always group before files, then sort within each group by the selected key. - Count: while filtering/searching show
N / 304 items; otherwise304 items. - Right side of each row shows creation date in mono; when sorting by time, emphasize this column.
- Empty result state:
No matching fileswith helper copy. - When dropdown is open, add a transparent fixed overlay that closes it on click.
- Parent directory row: non-root listings always have a fixed first row for "Back to parent", with a dashed icon border and muted text. It is excluded from filtering/search/sort and remains visible in empty directories. Breadcrumb segments, including root, are clickable with dotted underline affordance.
- Open behavior: directories navigate in place; files open in a new tab via
target=_blank, matching the external-open arrow at the row end.
Browser navigation to .md(.markdown) / .json(.geojson) / .csv(.tsv) renders a preview page instead of raw text. The preview shares the PreviewPage shell and uses the same URL as the file, so relative references resolve naturally.
- Shell: kicker based on permission wording -> serif title from filename -> clickable breadcrumb with "View raw" mono link on the right -> viewfinder content card -> footer credit. Loading/failure states use muted mono copy:
Loading.../Failed to parse · View raw. ?raw=1always returns original text; curl/scripts with non-HTML Accept headers automatically receive raw text.- Markdown: serif headings, mono code/tables, accent left border on blockquotes, rounded bordered images with
max-width:100%. Render with vendored marked. Raw HTML is always escaped and shown as visible text, never executed. - JSON: collapsible tree using native
details, first two levels expanded by default; type coloring for strings/number/boolean/null; lazy child construction; "show more" batches every 500 items; strings truncate at 200 characters and expand on click. Search traverses the full data tree and displays flat matches asfull path + valueup to 500 results. Toolbar meta showsObject · N keys · size. - CSV: first row is always header. RFC4180 parsing with quoted fields,
""escaping, and newlines inside fields. Auto-detect delimiter among, ; \t. Header click sorts columns with numeric comparison when sample values look numeric and ▲▼ indicators. Search filters by any cell. Meta showsN / total rows x M columns. DOM renders in batches of 1000 rows with "show more" for large files.
- Visible only on current network switch: in Settings under Listening Port. Row contains a title, helper copy, and Switch. Multi-interface copy: only selected network source can access it; other connected networks cannot. Single-interface copy: only current network can access it, and future networks will not. Default is off, meaning open on all local interfaces; when enabled, the share binds only to the selected source network.
- Online visitor device names: the line below the share address no longer shows only a count. It uses a small green dot and visitor summary copy. Unknown names fall back to IP suffixes; line is single-line with end truncation.
- Plain HTTP notice: two restrained gray
inkMuteplacements withlock.openicon: under Access in Settings and after a separator at the end of the "Can't connect?" bubble. Copy: same-network transfers are not encrypted; on public Wi-Fi, others on the same network may see content; avoid sensitive files there. Do not use a colored warning box.
Screen.text, a secondary page with back navigation: header is back arrow + "Text Transfer" + settings gear. Brand name appears only on the home screen. There are two independent one-way channels: sharedText for Mac -> phone and inbox for phone -> Mac.
Screen sections, top to bottom:
- QR card / empty state: service running with URL -> QRCard (172x172, padding 18, radius 14) + explanatory text + CopyPill + fallback mono address. Otherwise show empty copy: send text or enable receiving, then scan.
- Send text card:
surfaceradius 16, padding 16,lineborder. Section eyebrow ->PlainTextEditor(field, radius 12, mono 13, min-height 118, custom placeholder compatible with Chinese IME, accent caret) -> PrimaryBtn "Share" or "Update"; show "Withdraw" GhostBtn when already shared. - Allow receiving row:
surfaceradius 14, padding 14. Title + helper copy + Switch. Opt-in and off by default. - Inbox card: visible only when receiving is enabled or messages exist. Header = accent status dot + title + unread badge + Clear action. List shows up to 12 items, then a count. Each row has TextGlyph, source device/IP and relative time, text preview up to 3 selectable lines, copy success check, and delete button.
- Stop transfer: DangerBtn "Stop", visible only while service is running and there is shared text or receiving is enabled.
Required behavior:
- Token is session-scoped: updating text via
setSharedTextdoes not rotate the token, otherwise active visitors would be kicked and coexisting file-share links would break. Rotate only at session boundaries: changing share, stopping, clearing, or stopping text transfer. - Receive limits: each message <= 64 KB with 413 after read; inbox <= 100 items, dropping oldest. Only in-app indication, no system notifications.
- Receive pulse dot: Text Transfer entry and running state use
PulsingDotsize 6 when either send or receive is active. stopTextTransfer: withdraw text, clear draft, disable receiving, delete persistence, stop service, rotate token, and return home.- Persistence toggles default off and are separate: remember shared text versus remember received text. Remembering shared text restores the draft only and does not auto-broadcast.
- Web side: sent text page escapes plain text, has a large Copy button with
execCommandfallback because plain HTTP is not a secure context, and auto-links http(s). Receive form appears with the same conditions and placement as guest upload.
Settings Language control is a three-segment control matching Appearance segmented controls: Follow System / 中文 / English. Radius 9, height 34, gap 8, equal widths. Selected = accent background + white sans 13 semibold, no border. Unselected = surface + ink + line border, sans 13 medium. Language names are written in their own language; Follow System follows the current app language. Bind to persisted AppState.langPref values: system / zh / en.
Two language domains are independent:
- Native app uses Settings (
AppState.lang/Lang.currentsnapshot for menus and CLI). - Web UI is decided per request from browser
Accept-Languagewith descendingqvalues andq=0skipped. It must never read the app setting. The desktop app can be Chinese while the visitor's phone UI is English, or the reverse.
Copy categories: static strings use L enum keys with compiler-enforced exhaustiveness; interpolated/plural/order-sensitive strings use LStr; JavaScript-facing strings use i18nJSON, where jsEscape escapes < to block </script>. Adding a language requires adding branches to both L and LStr.
- Bilingual product UI: Simplified Chinese and English, routed through
Lang.swiftstring tables. Keep copy short and restrained. Use·as the separator. - Technical values such as IP, port, path, size, item count, and date always use mono.
- Destructive action verb: "Stop". Positive actions: choose files/folders, broadcast again, reshare.
- Permission wording follows state: "read-only share" / "read/write", not translated mechanically.
- Footer credit:
Powered by LocalShare · Read-only, adjusted when writable.
Mocks use a design canvas with side-by-side surfaces. Implementation should be responsive; these are proportional references:
- Desktop app screens: 400 x 720 borderless utility window.
- Desktop browser: about 940px wide, including 48px browser chrome with traffic lights, navigation buttons, and a mono HTTPS/address bar.
- Mobile client: iOS shell around 390 x 780 with compact address bar.
Configurable product settings should keep the exposed design tweaks: accent color, dark mode, and whether titles use serif.
Do: build themes from tokens; use mono for technical values; make Stop a solid red button; implement permission wording, port validation, search, and sort exactly; preserve shadows and radii.
Don't: gradient backgrounds, emoji, colorful filled icons, ghost-style Stop buttons, screen-local hard-coded "read-only" labels, submit-only port validation, or chips that highlight without filtering.