├── .gitignore ├── src ├── env.d.ts ├── icons │ ├── single.svg │ ├── landscape.svg │ ├── portrait.svg │ ├── multi.svg │ ├── github.svg │ ├── favicon.svg │ └── wind.svg ├── pages │ ├── test │ │ └── index.astro │ └── index.astro └── scripts │ └── magic.js ├── tsconfig.json ├── public ├── og.png └── favicon.svg ├── tailwind.config.cjs ├── astro.config.mjs ├── package.json ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | dist 4 | node_modules -------------------------------------------------------------------------------- /src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/base" 3 | } 4 | -------------------------------------------------------------------------------- /public/og.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IntToDouble/winded/HEAD/public/og.png -------------------------------------------------------------------------------- /tailwind.config.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | } 9 | -------------------------------------------------------------------------------- /src/icons/single.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/landscape.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/portrait.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /astro.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "astro/config"; 2 | 3 | import tailwind from "@astrojs/tailwind"; 4 | 5 | // https://astro.build/config 6 | export default defineConfig({ 7 | integrations: [tailwind()], 8 | build: { 9 | inlineStylesheets: "always", 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /src/icons/multi.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "astro", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "scripts": { 6 | "dev": "astro dev", 7 | "start": "astro dev", 8 | "build": "astro build", 9 | "preview": "astro preview", 10 | "astro": "astro" 11 | }, 12 | "dependencies": { 13 | "@astrojs/tailwind": "^4.0.0", 14 | "astro": "^2.7.0", 15 | "astro-icon": "^0.8.1", 16 | "tailwindcss": "^3.3.2" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/icons/github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/favicon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/wind.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WINDED 2 | 3 | [winded.inttodouble.com](https://winded.inttodouble.com/) 4 | 5 | WINDED simplifies responsive design by `iframe`-ing a single page across multiple breakpoints. 6 | 7 | _ChatGPT on why this is useful:_ 8 | 9 | > This functionality is highly useful as it enables developers to simultaneously view and assess how their webpage appears on different screen sizes, thus saving time and improving accuracy in creating adaptable, user-friendly designs. 10 | 11 | Though specifically created for Tailwind CSS, the preset breakpoints are valuable for almost any responsive application. 12 | 13 | ![winded](https://github.com/IntToDouble/winded/assets/3053339/1a2e1684-7316-422d-8a37-25157e813b96) 14 | 15 | ## Breakpoints 16 | 17 | https://tailwindcss.com/docs/responsive-design 18 | 19 | | Breakpoint prefix | Minimum width | 20 | | ----------------- | ------------- | 21 | | sm | 640px | 22 | | md | 768px | 23 | | lg | 1024px | 24 | | xl | 1280px | 25 | | 2xl | 1536px | 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 IntToDouble 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | -------------------------------------------------------------------------------- /src/pages/test/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { Icon } from "astro-icon"; 3 | --- 4 | 5 | 6 | 7 | 8 | 9 | Winded Test 10 | 11 | 12 |
13 | 14 |

WINDED

15 |
16 |
17 | 18 |

Reduce the time you spend testing responsive designs with Tailwind.

19 |
20 |

Resolution

21 |

$RESOLUTION

22 |
23 |
24 |

Visible Breakpoints

25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 |
33 |
34 |
35 |

USAGE

36 |

37 | Use the URL input field to enter the URL of the webpage you want to 38 | view. 39 |

40 |
41 |

42 | Breakpoints, viewing mode, and orientation can all be adjusted by the 43 | buttons to the right. 44 |

45 |
46 |
47 |

Troubleshooting

48 |

49 | If the page fails to load, make sure the 50 | 54 | X-Frame-Options 56 | header isn't the cluprit. We recommend using 57 | localhost to avoid this issue. 58 |

59 |
60 |
61 | 62 | 68 | 69 | 70 | 83 | -------------------------------------------------------------------------------- /src/scripts/magic.js: -------------------------------------------------------------------------------- 1 | const BREAKPOINTS = [ 2 | { 3 | label: "sm", 4 | width: 640, 5 | peerClass: "peer/sm", 6 | peerCheckedClass: "peer-checked/sm", 7 | }, 8 | { 9 | label: "md", 10 | width: 768, 11 | peerClass: "peer/md", 12 | peerCheckedClass: "peer-checked/md", 13 | }, 14 | { 15 | label: "lg", 16 | width: 1024, 17 | peerClass: "peer/lg", 18 | peerCheckedClass: "peer-checked/lg", 19 | }, 20 | { 21 | label: "xl", 22 | width: 1280, 23 | peerClass: "peer/xl", 24 | peerCheckedClass: "peer-checked/xl", 25 | }, 26 | { 27 | label: "2xl", 28 | width: 1536, 29 | peerClass: "peer/2xl", 30 | peerCheckedClass: "peer-checked/2xl", 31 | }, 32 | // TODO - ADD FULL 33 | ]; 34 | 35 | // TODO - SWTICH TO TEMPLATES 36 | // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template 37 | const createBreakpointContainer = ({ label, width }, url) => { 38 | const div = document.createElement("div"); 39 | div.id = label; 40 | div.className = `breakpoint-container`; 41 | div.innerHTML = ` 42 |
43 |

${label}

44 |
45 | 46 | `; 47 | 48 | const orientation = document.querySelector( 49 | "input[name=orientation]:checked" 50 | )?.value; 51 | 52 | const height = orientation === "landscape" ? width * 0.5625 : width * 1.7778; 53 | 54 | const iframe = div.querySelector("iframe"); 55 | iframe.style.height = `${height}px`; 56 | iframe.style.width = `${width}px`; 57 | 58 | return div; 59 | }; 60 | 61 | const getActiveBreakpoints = () => { 62 | const breakpoints = document.querySelectorAll("input[type=checkbox]:checked"); 63 | const activeBreakpoint = Array.from(breakpoints).map((b) => b.id); 64 | return BREAKPOINTS.filter((b) => activeBreakpoint.includes(b.label)); 65 | }; 66 | 67 | const setAllBreakpoints = (state) => { 68 | const checkboxes = document.querySelectorAll("input[type=checkbox]"); 69 | checkboxes.forEach((checkbox) => { 70 | checkbox.checked = state; 71 | }); 72 | }; 73 | 74 | const drawBreakpoints = () => { 75 | const url = document.getElementById("url-input").value; 76 | const panesContainer = document.getElementById("panes-container"); 77 | panesContainer.innerHTML = ""; 78 | 79 | // TODO - DON'T RECREATE ALL PANES WHEN IN SINGLE MODE 80 | const activeBreakpoints = getActiveBreakpoints(); 81 | activeBreakpoints.forEach((breakpoint) => { 82 | const elem = createBreakpointContainer(breakpoint, url); 83 | panesContainer.appendChild(elem); 84 | }); 85 | }; 86 | 87 | // Hello, DOM! 88 | document.addEventListener("DOMContentLoaded", (event) => { 89 | const urlInput = document.getElementById("url-input"); 90 | urlInput.value = 91 | localStorage.getItem("url") || "https://winded.inttodouble.com/test"; 92 | 93 | urlInput.addEventListener("change", (event) => { 94 | localStorage.setItem("url", event.target.value); 95 | drawBreakpoints(); 96 | }); 97 | 98 | // Orientation 99 | const radios = document.querySelectorAll("input[name=orientation]"); 100 | radios.forEach((radio) => { 101 | radio.addEventListener("change", drawBreakpoints); 102 | }); 103 | 104 | // Mode 105 | const modeRadios = document.querySelectorAll("input[name=mode]"); 106 | modeRadios.forEach((radio) => { 107 | radio.addEventListener("change", (e) => { 108 | if (e.target.value === "single") { 109 | setAllBreakpoints(false); 110 | const firstBox = document.querySelector("input[type=checkbox]"); 111 | firstBox.checked = true; 112 | } else { 113 | setAllBreakpoints(true); 114 | } 115 | 116 | drawBreakpoints(); 117 | }); 118 | }); 119 | 120 | // Breakpoints 121 | const breakpointBoxes = document.querySelectorAll("input[type=checkbox]"); 122 | breakpointBoxes.forEach((checkbox) => { 123 | checkbox.addEventListener("change", () => { 124 | const mode = document.querySelector("input[name=mode]:checked").value; 125 | if (mode === "single") { 126 | setAllBreakpoints(false); 127 | checkbox.checked = true; 128 | } 129 | drawBreakpoints(); 130 | }); 131 | }); 132 | 133 | const helpButton = document.getElementById("help-button"); 134 | helpButton.addEventListener("click", () => { 135 | urlInput.value = "https://winded.inttodouble.com/test"; 136 | drawBreakpoints(); 137 | }); 138 | 139 | drawBreakpoints(); 140 | }); 141 | -------------------------------------------------------------------------------- /src/pages/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { Icon } from "astro-icon"; 3 | --- 4 | 5 | 6 | 7 | WINDED | Tailwind Breakpoint Mastery 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
31 | 32 |
33 | 34 | 35 |

WINDED

36 |
37 |
38 | 39 | 186 | 187 | 188 | 189 |
190 | 195 |
198 | WINDED needs room to breathe. 199 |

200 | Please give your browser more pixels. 201 |
202 | 208 | 209 | 210 | 211 | 228 | --------------------------------------------------------------------------------