├── .github
├── FUNDING.yml
└── workflows
│ └── nodejs.yml
├── .gitignore
├── README.md
├── docs
├── adfree.html
├── css
│ ├── style.css
│ └── style.css.map
├── favicon.ico
├── favicon.svg
├── images
│ ├── android-chrome-192x192.png
│ ├── android-chrome-512x512.png
│ ├── apple-touch-icon.png
│ ├── chatgpt-api-playground.jpg
│ ├── default-image-thumbnail.jpg
│ ├── default-image.jpg
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── favicon.ico
│ ├── favicon.png
│ ├── favicon.svg
│ ├── featured.jpeg
│ ├── mstile-144x144.png
│ ├── mstile-150x150.png
│ ├── mstile-310x150.png
│ ├── mstile-310x310.png
│ ├── mstile-70x70.png
│ └── safari-pinned-tab.svg
├── index.html
├── js
│ ├── classes.js
│ ├── cryptography.js
│ ├── export.js
│ ├── index.js
│ ├── manageLocalStorage.js
│ ├── openAI.js
│ ├── templates.js
│ ├── utils.js
│ └── vendor
│ │ ├── bootstrap.bundle.min.js
│ │ ├── bootstrap.bundle.min.js.map
│ │ ├── crypto-js.min.js
│ │ └── marked.min.js
└── site.webmanifest
├── package-lock.json
├── package.json
├── scripts
└── generateAdFree.js
├── src
├── scss
│ ├── _main.scss
│ ├── _vendor.scss
│ ├── bootstrap
│ │ ├── _accordion.scss
│ │ ├── _alert.scss
│ │ ├── _badge.scss
│ │ ├── _breadcrumb.scss
│ │ ├── _button-group.scss
│ │ ├── _buttons.scss
│ │ ├── _card.scss
│ │ ├── _carousel.scss
│ │ ├── _close.scss
│ │ ├── _containers.scss
│ │ ├── _dropdown.scss
│ │ ├── _forms.scss
│ │ ├── _functions.scss
│ │ ├── _grid.scss
│ │ ├── _helpers.scss
│ │ ├── _images.scss
│ │ ├── _list-group.scss
│ │ ├── _maps.scss
│ │ ├── _mixins.scss
│ │ ├── _modal.scss
│ │ ├── _nav.scss
│ │ ├── _navbar.scss
│ │ ├── _offcanvas.scss
│ │ ├── _pagination.scss
│ │ ├── _placeholders.scss
│ │ ├── _popover.scss
│ │ ├── _progress.scss
│ │ ├── _reboot.scss
│ │ ├── _root.scss
│ │ ├── _spinners.scss
│ │ ├── _tables.scss
│ │ ├── _toasts.scss
│ │ ├── _tooltip.scss
│ │ ├── _transitions.scss
│ │ ├── _type.scss
│ │ ├── _utilities.scss
│ │ ├── _variables-dark.scss
│ │ ├── _variables.scss
│ │ ├── bootstrap-grid.scss
│ │ ├── bootstrap-reboot.scss
│ │ ├── bootstrap-utilities.scss
│ │ ├── bootstrap.scss
│ │ ├── forms
│ │ │ ├── _floating-labels.scss
│ │ │ ├── _form-check.scss
│ │ │ ├── _form-control.scss
│ │ │ ├── _form-range.scss
│ │ │ ├── _form-select.scss
│ │ │ ├── _form-text.scss
│ │ │ ├── _input-group.scss
│ │ │ ├── _labels.scss
│ │ │ └── _validation.scss
│ │ ├── helpers
│ │ │ ├── _clearfix.scss
│ │ │ ├── _color-bg.scss
│ │ │ ├── _colored-links.scss
│ │ │ ├── _position.scss
│ │ │ ├── _ratio.scss
│ │ │ ├── _stacks.scss
│ │ │ ├── _stretched-link.scss
│ │ │ ├── _text-truncation.scss
│ │ │ ├── _visually-hidden.scss
│ │ │ └── _vr.scss
│ │ ├── mixins
│ │ │ ├── _alert.scss
│ │ │ ├── _backdrop.scss
│ │ │ ├── _banner.scss
│ │ │ ├── _border-radius.scss
│ │ │ ├── _box-shadow.scss
│ │ │ ├── _breakpoints.scss
│ │ │ ├── _buttons.scss
│ │ │ ├── _caret.scss
│ │ │ ├── _clearfix.scss
│ │ │ ├── _color-mode.scss
│ │ │ ├── _color-scheme.scss
│ │ │ ├── _container.scss
│ │ │ ├── _deprecate.scss
│ │ │ ├── _forms.scss
│ │ │ ├── _gradients.scss
│ │ │ ├── _grid.scss
│ │ │ ├── _image.scss
│ │ │ ├── _list-group.scss
│ │ │ ├── _lists.scss
│ │ │ ├── _pagination.scss
│ │ │ ├── _reset-text.scss
│ │ │ ├── _resize.scss
│ │ │ ├── _table-variants.scss
│ │ │ ├── _text-truncate.scss
│ │ │ ├── _transition.scss
│ │ │ ├── _utilities.scss
│ │ │ └── _visually-hidden.scss
│ │ ├── utilities
│ │ │ └── _api.scss
│ │ └── vendor
│ │ │ └── _rfs.scss
│ └── style.scss
└── ts
│ ├── classes.ts
│ ├── cryptography.ts
│ ├── export.ts
│ ├── index.ts
│ ├── manageLocalStorage.ts
│ ├── openAI.ts
│ ├── templates.ts
│ └── utils.ts
└── tsconfig.json
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: kannansuresh
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: aneejian
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: ['https://www.buymeacoffee.com/aneejian', 'https://donorbox.org/change-case-excel-add-in-donation']
13 |
--------------------------------------------------------------------------------
/.github/workflows/nodejs.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
3 | name: Node.js CI
4 | on:
5 | workflow_dispatch:
6 | push:
7 | jobs:
8 | build:
9 | runs-on: ubuntu-latest
10 | strategy:
11 | matrix:
12 | node-version: [18.x]
13 | steps:
14 | - uses: actions/checkout@v3
15 | - name: Use Node.js ${{ matrix.node-version }}
16 | uses: actions/setup-node@v3
17 | with:
18 | node-version: ${{ matrix.node-version }}
19 | cache: 'npm'
20 | - run: npm install
21 | - run: npm run build --if-present
22 | - name: setup git config
23 | run: |
24 | git config user.name "GitHub Actions Bot"
25 | git config user.email "<>"
26 | - name: commit and push
27 | run: |
28 | git add --all
29 | git commit -m "Updated files." || echo "No changes to commit."
30 | git push || echo "No changes to push."
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Cache files
2 | .eslintcache
3 | .npm
4 | *.tsbuildinfo
5 | # Directories
6 | node_modules/
7 | # Env files # A good safeguard against accidentally publishing
8 | .env.
9 | .env.test
10 | # Logs
11 | *.log
12 | logs
13 | npm-debug.log*
14 | # Misc files
15 | .DS_Store
16 | notes.md
17 | dist
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ChatGPT API Playground
2 |
3 | [](https://github.com/kannansuresh/chatgpt-playground/actions/workflows/nodejs.yml)
4 |
5 | [](https://github.com/kannansuresh/chatgpt-playground/actions/workflows/pages/pages-build-deployment)
6 |
7 | Just like the ChatGPT Playground, this tool helps you in playing with the ChatGPT model. It is a simple web app that lets you have a chat with ChatGPT using the API.
8 |
9 | I suggest you to read our article on [Getting Started with ChatGPT API](https://aneejian.com/getting-started-chat-gpt-api-comprehensive-guide/) before using this tool. That will give you a good idea of what these fields represent.
10 |
11 | I suggest you to read our article on [Getting Started with ChatGPT API](https://aneejian.com/getting-started-chat-gpt-api-comprehensive-guide/) before using this tool. That will give you a good idea of what these fields represent.
12 |
13 | ## Demo
14 |
15 | ### [Demo Video](https://www.youtube.com/v/OYQ7Wfa6uU4)
16 |
17 | ### Features
18 |
19 | 1. Provide prompts as System, User, or Assistant.
20 | 2. Switch the roles of each message.
21 | 3. Add new messages.
22 | 4. Delete any irrelevant messages.
23 | 5. Export the chat as Markdown file.
24 | 6. Export the chat as HTML file.
25 | 7. Export the code that generated the chat as a Python file.
26 |
27 | ### Future plans
28 |
29 | - [x] Create a standalone tool and make the code open source.
30 | - [x] Implement HTML preview of responses.
31 | - [ ] Add more API settings like temperature, top_p, and max_tokens.
32 | - [ ] Add more programming language exports like JavaScript, C#, and PHP.
33 | - [ ] Option to save chats to local and load it later to continue conversation.
34 |
35 | ### FAQ
36 |
37 | #### How to get API Key?
38 |
39 | You can get API Key by visiting [platform.openai.com](https://platform.openai.com/account/api-keys) and clicking on "Get API Key" button.
40 |
41 | #### How does save API key work?
42 |
43 | API Key get stored in your browser's local storage. This means that it is stored in your browser and not on our site. The key is stored in an encrypted format and is not accessible to us.
44 |
45 | #### Can you access the chats I make?
46 |
47 | No, we cannot access the chats you make. This tool just acts as an interface for ChatGPT API. We do not have access or store any of your chats.
48 |
--------------------------------------------------------------------------------
/docs/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kannansuresh/chatgpt-playground/e454bf53d57aed2b05b7d132305acd5e15f0386f/docs/favicon.ico
--------------------------------------------------------------------------------
/docs/favicon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
24 |
--------------------------------------------------------------------------------
/docs/images/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kannansuresh/chatgpt-playground/e454bf53d57aed2b05b7d132305acd5e15f0386f/docs/images/android-chrome-192x192.png
--------------------------------------------------------------------------------
/docs/images/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kannansuresh/chatgpt-playground/e454bf53d57aed2b05b7d132305acd5e15f0386f/docs/images/android-chrome-512x512.png
--------------------------------------------------------------------------------
/docs/images/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kannansuresh/chatgpt-playground/e454bf53d57aed2b05b7d132305acd5e15f0386f/docs/images/apple-touch-icon.png
--------------------------------------------------------------------------------
/docs/images/chatgpt-api-playground.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kannansuresh/chatgpt-playground/e454bf53d57aed2b05b7d132305acd5e15f0386f/docs/images/chatgpt-api-playground.jpg
--------------------------------------------------------------------------------
/docs/images/default-image-thumbnail.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kannansuresh/chatgpt-playground/e454bf53d57aed2b05b7d132305acd5e15f0386f/docs/images/default-image-thumbnail.jpg
--------------------------------------------------------------------------------
/docs/images/default-image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kannansuresh/chatgpt-playground/e454bf53d57aed2b05b7d132305acd5e15f0386f/docs/images/default-image.jpg
--------------------------------------------------------------------------------
/docs/images/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kannansuresh/chatgpt-playground/e454bf53d57aed2b05b7d132305acd5e15f0386f/docs/images/favicon-16x16.png
--------------------------------------------------------------------------------
/docs/images/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kannansuresh/chatgpt-playground/e454bf53d57aed2b05b7d132305acd5e15f0386f/docs/images/favicon-32x32.png
--------------------------------------------------------------------------------
/docs/images/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kannansuresh/chatgpt-playground/e454bf53d57aed2b05b7d132305acd5e15f0386f/docs/images/favicon.ico
--------------------------------------------------------------------------------
/docs/images/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kannansuresh/chatgpt-playground/e454bf53d57aed2b05b7d132305acd5e15f0386f/docs/images/favicon.png
--------------------------------------------------------------------------------
/docs/images/favicon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
24 |
--------------------------------------------------------------------------------
/docs/images/featured.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kannansuresh/chatgpt-playground/e454bf53d57aed2b05b7d132305acd5e15f0386f/docs/images/featured.jpeg
--------------------------------------------------------------------------------
/docs/images/mstile-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kannansuresh/chatgpt-playground/e454bf53d57aed2b05b7d132305acd5e15f0386f/docs/images/mstile-144x144.png
--------------------------------------------------------------------------------
/docs/images/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kannansuresh/chatgpt-playground/e454bf53d57aed2b05b7d132305acd5e15f0386f/docs/images/mstile-150x150.png
--------------------------------------------------------------------------------
/docs/images/mstile-310x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kannansuresh/chatgpt-playground/e454bf53d57aed2b05b7d132305acd5e15f0386f/docs/images/mstile-310x150.png
--------------------------------------------------------------------------------
/docs/images/mstile-310x310.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kannansuresh/chatgpt-playground/e454bf53d57aed2b05b7d132305acd5e15f0386f/docs/images/mstile-310x310.png
--------------------------------------------------------------------------------
/docs/images/mstile-70x70.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kannansuresh/chatgpt-playground/e454bf53d57aed2b05b7d132305acd5e15f0386f/docs/images/mstile-70x70.png
--------------------------------------------------------------------------------
/docs/images/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
44 |
--------------------------------------------------------------------------------
/docs/js/classes.js:
--------------------------------------------------------------------------------
1 | export class payloadRole {
2 | constructor(role, icon, short, placeholder) {
3 | this.role = role;
4 | this.icon = icon;
5 | this.short = short;
6 | this.placeholder = placeholder;
7 | }
8 | }
9 | export class payloadMessage {
10 | constructor(role, content) {
11 | this.role = role;
12 | this.content = content;
13 | }
14 | }
15 | export class chatGPT {
16 | constructor(...args) {
17 | this.model = 'gpt-3.5-turbo';
18 | this.stream = true;
19 | this.endPoint = 'https://api.openai.com/v1/chat/completions';
20 | this.apiKey = '';
21 | if (args.length === 0) {
22 | this.apiKey = '';
23 | this.payloadMessages = [];
24 | }
25 | else if (args.length === 1) {
26 | this.apiKey = args[0];
27 | this.payloadMessages = [];
28 | }
29 | else if (args.length === 2) {
30 | this.apiKey = args[0];
31 | this.payloadMessages = args[1];
32 | }
33 | else {
34 | this.model = args[0];
35 | this.stream = args[1];
36 | this.endPoint = args[2];
37 | this.payloadMessages = args[3];
38 | }
39 | }
40 | getRequestData() {
41 | return {
42 | method: 'POST',
43 | headers: {
44 | 'Content-Type': 'application/json',
45 | Authorization: `Bearer ${this.apiKey}`,
46 | },
47 | body: JSON.stringify({
48 | model: this.model,
49 | messages: this.payloadMessages,
50 | stream: this.stream,
51 | }),
52 | };
53 | }
54 | }
55 | chatGPT.roles = {
56 | system: new payloadRole('system', '🧠', 'sys', ''),
57 | user: new payloadRole('user', '👤', 'usr', 'Enter a user message here.'),
58 | assistant: new payloadRole('assistant', '🤖', 'ast', 'Enter an assistant message here.'),
59 | };
60 |
--------------------------------------------------------------------------------
/docs/js/cryptography.js:
--------------------------------------------------------------------------------
1 | export function encryptDecrypt(plainOrEncryptedString, performEncryption = false) {
2 | try {
3 | const userHash = generateUserHash().toString();
4 | if (performEncryption)
5 | return CryptoJS.AES.encrypt(plainOrEncryptedString, userHash).toString();
6 | return CryptoJS.AES.decrypt(plainOrEncryptedString, userHash).toString(CryptoJS.enc.Utf8);
7 | }
8 | catch (error) {
9 | return plainOrEncryptedString;
10 | }
11 | }
12 | export function encrypt(plainString) {
13 | const userHash = generateUserHash().toString();
14 | return CryptoJS.AES.encrypt(plainString, userHash).toString();
15 | }
16 | export function decrypt(encryptedString) {
17 | const userHash = generateUserHash().toString();
18 | return CryptoJS.AES.decrypt(encryptedString, userHash).toString(CryptoJS.enc.Utf8);
19 | }
20 | // Generate a hash based on the user's browser and machine properties
21 | function generateUserHash() {
22 | const userAgent = navigator.userAgent;
23 | const platform = userAgent.indexOf('Win') !== -1
24 | ? 'Windows'
25 | : userAgent.indexOf('Mac') !== -1
26 | ? 'MacOS'
27 | : userAgent.indexOf('X11') !== -1
28 | ? 'UNIX'
29 | : userAgent.indexOf('Linux') !== -1
30 | ? 'Linux'
31 | : 'Unknown OS';
32 | const hashInput = userAgent + platform;
33 | const hash = CryptoJS.SHA256(hashInput); // use a cryptographic hash function (e.g. SHA-256) to generate a hash
34 | return hash;
35 | }
36 |
--------------------------------------------------------------------------------
/docs/js/export.js:
--------------------------------------------------------------------------------
1 | import * as templates from './templates.js';
2 | import * as utils from './utils.js';
3 | const htmlTemplate = templates.html;
4 | const pythonTemplate = templates.python;
5 | const brand = {
6 | publisher: 'Aneejian',
7 | productName: 'ChatGPT Playground',
8 | url: 'https://aneejian.com/chatgpt-playground/',
9 | };
10 | const brandLineMd = `Downloaded from [${brand.publisher} ${brand.productName}](${brand.url})`;
11 | function getChats() {
12 | const textAreas = document.querySelectorAll('textarea');
13 | if (!textAreas.length) {
14 | utils.showModal('Message Export', 'No messages to download.', '');
15 | return '';
16 | }
17 | const text = Array.from(textAreas)
18 | .map(t => {
19 | const value = t.value.trim();
20 | if (!value)
21 | return '';
22 | const roleType = t.parentElement?.querySelector('button')?.getAttribute('data-role-type')?.toUpperCase() || 'UNKNOWN';
23 | return `**${roleType}**\n\n${value}\n\n---\n\n`;
24 | })
25 | .join('');
26 | return text;
27 | }
28 | export function downloadMarkdown() {
29 | const text = getChats();
30 | if (!text?.trim()) {
31 | utils.showModal('Markdown Export', 'No messages to download. Enter at least one prompt.', '');
32 | return;
33 | }
34 | const { dateString, timeString } = utils.getDateTimeStrings();
35 | const filename = `Aneejian-ChatGPT-Playground-${dateString}-${timeString}.md`;
36 | const markdownText = `# Aneejian - ChatGPT Playground Export\n\n${text}\n\n${brandLineMd} on ${dateString} at ${timeString}`;
37 | utils.createDownloadLink(filename, markdownText, 'text/plain');
38 | }
39 | export function downloadHTML() {
40 | let text = getChats();
41 | if (!text?.trim()) {
42 | utils.showModal('HTML Export', 'No messages to download. Enter at least one prompt.', '');
43 | return;
44 | }
45 | const { dateString, timeString } = utils.getDateTimeStrings();
46 | const filename = `Aneejian-ChatGPT-Playground-${dateString}-${timeString}.html`;
47 | // @ts-ignore
48 | text = marked.parse(`${text}\n\n${brandLineMd} on ${dateString} at ${timeString}`);
49 | text = htmlTemplate.replace('', text);
50 | utils.createDownloadLink(filename, text, 'text/html');
51 | }
52 | export function downloadPython(messages, model) {
53 | if (!messages.length) {
54 | utils.showModal('Python Export', 'No messages to download. Enter at least one prompt.', '');
55 | return;
56 | }
57 | const pythonCode = pythonTemplate.replace('', model).replace('', JSON.stringify(messages));
58 | const { dateString, timeString } = utils.getDateTimeStrings();
59 | const filename = `Aneejian-ChatGPT-Playground-${dateString}-${timeString}.py`;
60 | utils.createDownloadLink(filename, pythonCode, 'text/html');
61 | }
62 |
--------------------------------------------------------------------------------
/docs/js/manageLocalStorage.js:
--------------------------------------------------------------------------------
1 | import * as crypto from './cryptography.js';
2 | const LOCAL_STORAGE_API_KEY = 'chatGPTPlaygroundAPIKey';
3 | const LOCAL_STORAGE_SAVE_KEY = 'chatGPTPlaygroundSaveAPIChoice';
4 | const LOCAL_STORAGE_MODEL_KEY = 'chatGPTPlaygroundModel';
5 | // Get the API key from local storage
6 | export function getAPIKey() {
7 | const encryptedString = localStorage.getItem(LOCAL_STORAGE_API_KEY);
8 | if (encryptedString) {
9 | try {
10 | const decryptedString = crypto.decrypt(encryptedString);
11 | return decryptedString;
12 | }
13 | catch (error) {
14 | console.log('Error decrypting API key: ' + error);
15 | return '';
16 | }
17 | }
18 | }
19 | // Save the API key to local storage
20 | export function setAPIKey(apiKey) {
21 | const encryptedString = crypto.encrypt(apiKey);
22 | localStorage.setItem(LOCAL_STORAGE_API_KEY, encryptedString);
23 | }
24 | // Save the model to local storage
25 | export function setModel(model) {
26 | localStorage.setItem(LOCAL_STORAGE_MODEL_KEY, model);
27 | }
28 | // Get the model from local storage
29 | export function getModel() {
30 | const model = localStorage.getItem(LOCAL_STORAGE_MODEL_KEY);
31 | if (model)
32 | return model;
33 | }
34 | // Get the user's choice to save the API key to local storage
35 | export function getSaveAPIChoice() {
36 | const saveAPIKeyChoice = localStorage.getItem(LOCAL_STORAGE_SAVE_KEY);
37 | if (saveAPIKeyChoice)
38 | return saveAPIKeyChoice === 'true';
39 | return false;
40 | }
41 | // Save the user's choice to save the API key to local storage
42 | export function setSaveAPIChoice(saveAPIKeyChoice) {
43 | localStorage.setItem(LOCAL_STORAGE_SAVE_KEY, saveAPIKeyChoice.toString());
44 | if (!saveAPIKeyChoice) {
45 | localStorage.removeItem(LOCAL_STORAGE_API_KEY);
46 | }
47 | }
48 | export function initializeForm(checkbox, inputField) {
49 | const saveChoice = getSaveAPIChoice();
50 | checkbox.checked = saveChoice;
51 | if (saveChoice) {
52 | const savedKey = getAPIKey();
53 | if (savedKey)
54 | inputField.value = savedKey;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/docs/js/openAI.js:
--------------------------------------------------------------------------------
1 | import { chatGPT } from './classes.js';
2 | import { resizeTextarea, getPreviewHtml } from './utils.js';
3 | let reader;
4 | export function stopStream() {
5 | console.log('Reader is: ' + reader);
6 | if (reader) {
7 | reader.cancel();
8 | }
9 | }
10 | export async function openAIChatComplete(gptData, textArea) {
11 | const previewDiv = textArea.parentElement?.querySelector('.preview');
12 | const url = gptData.endPoint;
13 | const requestData = gptData.getRequestData();
14 | let response;
15 | try {
16 | response = await fetch(url, requestData);
17 | // check for response errors
18 | if (!response.ok) {
19 | const error = await response.json();
20 | throw new Error(`${error.error.code}\n${error.error.message}`);
21 | }
22 | reader = response.body?.getReader();
23 | let responseText = '';
24 | const onData = (chunk) => {
25 | const textDecoder = new TextDecoder();
26 | const jsonString = textDecoder.decode(chunk, { stream: true });
27 | let jsonStrings = jsonString.split('data:');
28 | jsonStrings = jsonStrings.map(str => {
29 | if (str.includes('[DONE]')) {
30 | return str.replace('[DONE]', '');
31 | }
32 | return str;
33 | });
34 | jsonStrings = jsonStrings.map(str => str.trim()).filter(str => str.length > 0);
35 | textArea.classList.remove('hidden');
36 | previewDiv.classList.add('hidden');
37 | for (let i = 0; i < jsonStrings.length; i++) {
38 | const responseData = JSON.parse(jsonStrings[i]);
39 | const choices = responseData.choices;
40 | if (choices && choices.length > 0) {
41 | const delta = choices[0].delta;
42 | if (delta && delta.content) {
43 | const content = delta.content;
44 | responseText += content;
45 | updateTextAreaAndPreview(textArea, previewDiv, content);
46 | }
47 | }
48 | }
49 | };
50 | const onDone = () => {
51 | updateTextAreaAndPreview(textArea, previewDiv, responseText, true);
52 | };
53 | const read = () => {
54 | return reader?.read().then(({ done, value }) => {
55 | if (done) {
56 | onDone();
57 | return;
58 | }
59 | onData(value);
60 | return read();
61 | });
62 | };
63 | await read();
64 | return { result: true, response: responseText.trim() };
65 | }
66 | catch (error) {
67 | const errorMsg = `${error}`;
68 | updateTextAreaAndPreview(textArea, previewDiv, errorMsg, true, true);
69 | console.log(errorMsg);
70 | return { result: false, response: errorMsg };
71 | }
72 | finally {
73 | textArea.placeholder = chatGPT.roles['assistant'].placeholder;
74 | }
75 | }
76 | function updateTextAreaAndPreview(textArea, previewDiv, text, responseComplete = false, error = false) {
77 | textArea.value += text;
78 | textArea.value = textArea.value.trimStart();
79 | // @ts-ignore
80 | previewDiv.innerHTML = getPreviewHtml(textArea.value);
81 | resizeTextarea(textArea);
82 | textArea.scrollHeight;
83 | if (responseComplete) {
84 | textArea.value = error ? text : text.trim();
85 | // @ts-ignore
86 | previewDiv.innerHTML = getPreviewHtml(textArea.value);
87 | resizeTextarea(textArea);
88 | textArea.classList.add('hidden');
89 | previewDiv.classList.remove('hidden');
90 | }
91 | }
92 | export default openAIChatComplete;
93 |
--------------------------------------------------------------------------------
/docs/js/templates.js:
--------------------------------------------------------------------------------
1 | export const html = `
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Aneejian ChatGPT Playground Export
10 |
11 |
12 |
13 |
14 |
Aneejian ChatGPT Playground Export
15 |
16 |
17 |
18 |
19 | `;
20 | export const python = `
21 | # code generated by Aneejian's ChatGPT Playground
22 | # https://aneejian.com/chatgpt-api-playground
23 | import openai
24 | API_KEY = 'you api key here'
25 | openai.api_key = API_KEY
26 | completion = openai.ChatCompletion.create(
27 | model="",
28 | messages=
29 | )`;
30 |
--------------------------------------------------------------------------------
/docs/js/utils.js:
--------------------------------------------------------------------------------
1 | // import { stopStream } from './openAI';
2 | export function resizeTextarea(textarea) {
3 | textarea.style.height = 'auto';
4 | textarea.style.height = `${textarea.scrollHeight}px`;
5 | textarea.rows = textarea.value.split('\n').length > 1 ? textarea.value.split('\n').length : 1;
6 | ensureButtonInView();
7 | }
8 | function ensureButtonInView() {
9 | const button = document.getElementById('stopGenerationBtn');
10 | if (!button)
11 | return;
12 | const buttonRect = button.getBoundingClientRect();
13 | const inViewPort = buttonRect.top >= 0 && buttonRect.left >= 0 && buttonRect.bottom <= window.innerHeight && buttonRect.right <= window.innerWidth;
14 | if (!inViewPort) {
15 | button.scrollIntoView({ behavior: 'smooth', block: 'center' }); // scroll to element
16 | }
17 | }
18 | export function deleteMessage(messageToDelete) {
19 | try {
20 | messageToDelete.parentElement?.remove();
21 | }
22 | catch (err) {
23 | console.error('Error deleting message:', err);
24 | }
25 | }
26 | export function disableOrEnableElements(disable = true) {
27 | const buttons = document.querySelectorAll('button');
28 | const textAreas = document.querySelectorAll('textarea');
29 | const elements = [...buttons, ...textAreas];
30 | const filteredElements = Array.from(elements).filter(element => !element.classList.contains('is-disabled'));
31 | filteredElements.forEach(element => {
32 | element.disabled = disable;
33 | });
34 | }
35 | export function addSpinner(messagesContainer) {
36 | disableOrEnableElements(true);
37 | const placeholderDiv = document.createElement('div');
38 | placeholderDiv.id = 'placeholderDiv';
39 | const stopGeneratingButton = document.createElement('button');
40 | stopGeneratingButton.className = 'btn btn-danger btn-sm mb-2 mt-2';
41 | stopGeneratingButton.textContent = 'Stop Generating';
42 | stopGeneratingButton.style.display = 'block';
43 | stopGeneratingButton.type = 'button';
44 | stopGeneratingButton.id = 'stopGenerationBtn';
45 | const loadingParagraph = document.createElement('p');
46 | loadingParagraph.textContent = 'Fetching response';
47 | loadingParagraph.className = 'loading';
48 | placeholderDiv.appendChild(loadingParagraph);
49 | placeholderDiv.appendChild(stopGeneratingButton);
50 | messagesContainer.appendChild(placeholderDiv);
51 | return placeholderDiv;
52 | }
53 | export function removeSpinner() {
54 | const spinnerDiv = document.getElementById('placeholderDiv');
55 | if (spinnerDiv)
56 | spinnerDiv.remove();
57 | disableOrEnableElements(false);
58 | }
59 | export function getDateTimeStrings() {
60 | const now = new Date();
61 | const dateOptions = { year: 'numeric', month: '2-digit', day: '2-digit' };
62 | const timeOptions = {
63 | hour: '2-digit',
64 | minute: '2-digit',
65 | second: '2-digit',
66 | hour12: false,
67 | };
68 | // @ts-ignore
69 | const dateString = now.toLocaleDateString(undefined, dateOptions).replace(/\//g, '-');
70 | // @ts-ignore
71 | const timeString = now.toLocaleTimeString(undefined, timeOptions).replace(/:/g, '-');
72 | return { dateString, timeString };
73 | }
74 | export function createDownloadLink(filename, data, type) {
75 | const blob = new Blob([data], {
76 | type,
77 | });
78 | const url = window.URL.createObjectURL(blob);
79 | const a = document.createElement('a');
80 | a.href = url;
81 | a.download = filename;
82 | a.click();
83 | window.URL.revokeObjectURL(url);
84 | a.remove();
85 | }
86 | export function showModal(titleString = '', bodyString = '', buttonString = '', closeButtonString = 'Close', buttonFunction = null) {
87 | const title = document.getElementById('modalTitle');
88 | const body = document.getElementById('modalBody');
89 | const button = document.getElementById('modalButton');
90 | const closeButton = document.getElementById('modalCloseButton');
91 | title.textContent = titleString;
92 | body.innerHTML = bodyString;
93 | button.textContent = buttonString;
94 | closeButton.textContent = closeButtonString || 'Close';
95 | if (!buttonString) {
96 | button.style.display = 'none';
97 | }
98 | else {
99 | button.style.display = 'block';
100 | if (buttonFunction != null) {
101 | button.addEventListener('click', e => {
102 | buttonFunction('adfree.html');
103 | });
104 | }
105 | }
106 | // @ts-ignore
107 | const myModal = new bootstrap.Modal(document.getElementById('modal'));
108 | // @ts-ignore
109 | myModal.show();
110 | }
111 | // function to navigate to a url
112 | export const navigateTo = (url) => {
113 | window.location.href = url;
114 | };
115 | export function getPreviewHtml(text) {
116 | // const regex = /(?');
118 | // @ts-ignore
119 | return marked.parse(text);
120 | }
121 |
--------------------------------------------------------------------------------
/docs/site.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ChatGPT Play Ground | Aneejian",
3 | "short_name": "ChatGPT Play Ground | Aneejian",
4 | "description": "ChatGPT Play Ground - run ChatGPT with your API key.",
5 | "start_url": "/",
6 | "icons": [
7 | {
8 | "src": "/android-chrome-192x192.png",
9 | "sizes": "192x192",
10 | "type": "image/png"
11 | },
12 | {
13 | "src": "/android-chrome-512x512.png",
14 | "sizes": "512x512",
15 | "type": "image/png"
16 | }
17 | ],
18 | "theme_color": "#ffffff",
19 | "background_color": "#ffffff",
20 | "display": "standalone"
21 | }
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "chatgpt-playground",
3 | "version": "0.0.1",
4 | "description": "A playground for ChatGPT API.",
5 | "main": "index.ts",
6 | "scripts": {
7 | "build": "tsc && npm run scss && npm run generateAdFree",
8 | "dev": "npm-run-all --parallel live tscWatch scssWatch",
9 | "compile": "tsc && scss",
10 | "live": "live-server --port=3000 --open=docs/index.html",
11 | "scssWatch": "sass --watch src/scss/style.scss:docs/css/style.css",
12 | "scss": "sass src/scss/style.scss:docs/css/style.css",
13 | "tscWatch": "tsc -w",
14 | "generateAdFree": "node scripts/generateAdFree.js"
15 | },
16 | "repository": {
17 | "type": "git",
18 | "url": "git+https://github.com/kannansuresh/chatgpt-playground.git"
19 | },
20 | "keywords": [
21 | "ChatGPT",
22 | "API"
23 | ],
24 | "author": "Kannan Suresh",
25 | "license": "ISC",
26 | "bugs": {
27 | "url": "https://github.com/kannansuresh/chatgpt-playground/issues"
28 | },
29 | "homepage": "https://github.com/kannansuresh/chatgpt-playground#readme",
30 | "devDependencies": {
31 | "@types/crypto-js": "^4.1.1",
32 | "@types/marked": "^4.0.8",
33 | "fs": "^0.0.1-security",
34 | "npm-run-all": "^4.1.5",
35 | "sass": "^1.59.2",
36 | "typescript": "^4.9.5"
37 | },
38 | "dependencies": {
39 | "crypto-js": "^4.1.1",
40 | "marked": "^4.2.12"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/scripts/generateAdFree.js:
--------------------------------------------------------------------------------
1 | const { existsSync, unlinkSync, readFileSync, writeFileSync, createReadStream, createWriteStream } = require('fs');
2 | const path = require('path');
3 |
4 | const rootDirPath = path.resolve(__dirname, '..');
5 | const inputFilePath = path.resolve(rootDirPath, 'docs', 'index.html');
6 | const outputFilePath = path.resolve(rootDirPath, 'docs', 'adfree.html');
7 |
8 | if (existsSync(outputFilePath)) {
9 | unlinkSync(outputFilePath);
10 | }
11 |
12 | const readStream = createReadStream(inputFilePath, { encoding: 'utf-8' });
13 | const writeStream = createWriteStream(outputFilePath, { encoding: 'utf-8' });
14 |
15 | let modifiedContent = '';
16 |
17 | const startString = '';
18 | const endString = '';
19 | let skip = false;
20 |
21 | readStream.on('data', chunk => {
22 | const lines = chunk.split('\n');
23 | for (let line of lines) {
24 | if (line.includes(startString)) {
25 | skip = true;
26 | continue;
27 | }
28 | if (line.includes(endString)) {
29 | skip = false;
30 | continue;
31 | }
32 | if (!skip) modifiedContent += line + '\n';
33 | }
34 | });
35 |
36 | readStream.on('close', () => {
37 | writeStream.write(modifiedContent);
38 | writeStream.end();
39 | });
40 |
41 | readStream.on('error', err => {
42 | console.error(`Error while reading file: ${inputFilePath}`);
43 | console.error(err);
44 | });
45 |
46 | writeStream.on('finish', () => {
47 | console.log(`The file ${outputFilePath} has been created!`);
48 | });
49 |
50 | writeStream.on('error', err => {
51 | console.error(`Error while writing file: ${outputFilePath}`);
52 | console.error(err);
53 | });
54 |
--------------------------------------------------------------------------------
/src/scss/_main.scss:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: 'Josefin Sans', sans-serif;
3 | }
4 |
5 | h1 {
6 | font-weight: 300;
7 | }
8 |
9 | a {
10 | text-decoration: none;
11 | }
12 |
13 | .main-layout {
14 | min-height: 100vh;
15 | min-height: 100dvh;
16 | display: grid;
17 | grid-template-rows: auto 1fr auto auto;
18 | }
19 |
20 | .hidden {
21 | display: none;
22 | }
23 |
24 | .navbar-brand {
25 | font-family: 'Kaushan Script', cursive;
26 | font-size: xx-large;
27 | font-weight: 500;
28 | color: #fd6c3f !important;
29 | letter-spacing: 4px;
30 | }
31 |
32 | .message-text {
33 | overflow: hidden;
34 | resize: none;
35 | // min-height: 45px;
36 | ::-webkit-scrollbar {
37 | width: 0px;
38 | background: transparent;
39 | }
40 | ::-webkit-scrollbar-track {
41 | background: transparent;
42 | }
43 | ::-webkit-scrollbar-thumb {
44 | background: transparent;
45 | }
46 | }
47 |
48 | .api-key-text,
49 | .role-switch,
50 | .role-system,
51 | .message-delete {
52 | user-select: none;
53 | border: var(--bs-border-width) solid var(--bs-border-color) !important;
54 | }
55 |
56 | .api-key-text {
57 | padding-right: 1rem !important;
58 | }
59 |
60 | .message-delete {
61 | font-size: 10px;
62 | width: min-content;
63 | }
64 |
65 | #chatgpt-form div > label {
66 | font-size: medium;
67 | }
68 |
69 | .role-switch {
70 | cursor: pointer;
71 | text-align: center;
72 | }
73 |
74 | .text-small {
75 | font-size: 0.8rem !important;
76 | line-height: 0rem !important;
77 | }
78 |
79 | .input-group .btn {
80 | padding: 6px 3px !important;
81 | border-radius: 0px !important;
82 | }
83 |
84 | #chatgpt-form {
85 | textarea {
86 | padding-top: 0.4rem !important;
87 | border-radius: 0px !important;
88 | outline: none !important;
89 | }
90 | input {
91 | border-radius: 0px !important;
92 | }
93 | }
94 |
95 | .message-delete {
96 | min-width: 1rem !important;
97 | }
98 |
99 | .input-group-text {
100 | border-radius: 0px !important;
101 | }
102 |
103 | pre {
104 | background-color: #282a36;
105 | padding: 1rem;
106 | }
107 |
108 | // main {
109 | // min-height: calc(100vh - 148px) !important;
110 | // }
111 |
112 | #chatgpt-model {
113 | option {
114 | cursor: pointer !important;
115 | padding: 1rem !important;
116 | }
117 | }
118 |
119 | // loading ellipsis animation
120 |
121 | .loading:after {
122 | overflow: hidden;
123 | display: inline-block;
124 | vertical-align: bottom;
125 | -webkit-animation: ellipsis steps(4, end) 900ms infinite;
126 | animation: ellipsis steps(4, end) 900ms infinite;
127 | content: '\2026'; /* ascii code for the ellipsis character */
128 | width: 0px;
129 | }
130 |
131 | @keyframes ellipsis {
132 | to {
133 | width: 1.25em;
134 | }
135 | }
136 |
137 | @-webkit-keyframes ellipsis {
138 | to {
139 | width: 1.25em;
140 | }
141 | }
142 |
143 | // end of loading ellipsis animation.
144 |
145 | .preview p:last-child {
146 | margin-bottom: 0 !important;
147 | padding-bottom: 0 !important;
148 | }
149 |
150 | .form-control:focus {
151 | box-shadow: none !important;
152 | border-color: #ced4da;
153 | }
154 |
--------------------------------------------------------------------------------
/src/scss/_vendor.scss:
--------------------------------------------------------------------------------
1 | @import 'bootstrap/bootstrap.scss';
2 | @import url('https://fonts.googleapis.com/css2?family=Josefin+Sans:wght@300;400&family=Kaushan+Script&display=swap');
3 |
4 | table {
5 | @extend .table !optional;
6 | @extend .table-hover !optional;
7 | }
8 |
--------------------------------------------------------------------------------
/src/scss/bootstrap/_accordion.scss:
--------------------------------------------------------------------------------
1 | //
2 | // Base styles
3 | //
4 |
5 | .accordion {
6 | // scss-docs-start accordion-css-vars
7 | --#{$prefix}accordion-color: #{$accordion-color};
8 | --#{$prefix}accordion-bg: #{$accordion-bg};
9 | --#{$prefix}accordion-transition: #{$accordion-transition};
10 | --#{$prefix}accordion-border-color: #{$accordion-border-color};
11 | --#{$prefix}accordion-border-width: #{$accordion-border-width};
12 | --#{$prefix}accordion-border-radius: #{$accordion-border-radius};
13 | --#{$prefix}accordion-inner-border-radius: #{$accordion-inner-border-radius};
14 | --#{$prefix}accordion-btn-padding-x: #{$accordion-button-padding-x};
15 | --#{$prefix}accordion-btn-padding-y: #{$accordion-button-padding-y};
16 | --#{$prefix}accordion-btn-color: #{$accordion-button-color};
17 | --#{$prefix}accordion-btn-bg: #{$accordion-button-bg};
18 | --#{$prefix}accordion-btn-icon: #{escape-svg($accordion-button-icon)};
19 | --#{$prefix}accordion-btn-icon-width: #{$accordion-icon-width};
20 | --#{$prefix}accordion-btn-icon-transform: #{$accordion-icon-transform};
21 | --#{$prefix}accordion-btn-icon-transition: #{$accordion-icon-transition};
22 | --#{$prefix}accordion-btn-active-icon: #{escape-svg($accordion-button-active-icon)};
23 | --#{$prefix}accordion-btn-focus-border-color: #{$accordion-button-focus-border-color};
24 | --#{$prefix}accordion-btn-focus-box-shadow: #{$accordion-button-focus-box-shadow};
25 | --#{$prefix}accordion-body-padding-x: #{$accordion-body-padding-x};
26 | --#{$prefix}accordion-body-padding-y: #{$accordion-body-padding-y};
27 | --#{$prefix}accordion-active-color: #{$accordion-button-active-color};
28 | --#{$prefix}accordion-active-bg: #{$accordion-button-active-bg};
29 | // scss-docs-end accordion-css-vars
30 | }
31 |
32 | .accordion-button {
33 | position: relative;
34 | display: flex;
35 | align-items: center;
36 | width: 100%;
37 | padding: var(--#{$prefix}accordion-btn-padding-y) var(--#{$prefix}accordion-btn-padding-x);
38 | @include font-size($font-size-base);
39 | color: var(--#{$prefix}accordion-btn-color);
40 | text-align: left; // Reset button style
41 | background-color: var(--#{$prefix}accordion-btn-bg);
42 | border: 0;
43 | @include border-radius(0);
44 | overflow-anchor: none;
45 | @include transition(var(--#{$prefix}accordion-transition));
46 |
47 | &:not(.collapsed) {
48 | color: var(--#{$prefix}accordion-active-color);
49 | background-color: var(--#{$prefix}accordion-active-bg);
50 | box-shadow: inset 0 calc(-1 * var(--#{$prefix}accordion-border-width)) 0 var(--#{$prefix}accordion-border-color); // stylelint-disable-line function-disallowed-list
51 |
52 | &::after {
53 | background-image: var(--#{$prefix}accordion-btn-active-icon);
54 | transform: var(--#{$prefix}accordion-btn-icon-transform);
55 | }
56 | }
57 |
58 | // Accordion icon
59 | &::after {
60 | flex-shrink: 0;
61 | width: var(--#{$prefix}accordion-btn-icon-width);
62 | height: var(--#{$prefix}accordion-btn-icon-width);
63 | margin-left: auto;
64 | content: "";
65 | background-image: var(--#{$prefix}accordion-btn-icon);
66 | background-repeat: no-repeat;
67 | background-size: var(--#{$prefix}accordion-btn-icon-width);
68 | @include transition(var(--#{$prefix}accordion-btn-icon-transition));
69 | }
70 |
71 | &:hover {
72 | z-index: 2;
73 | }
74 |
75 | &:focus {
76 | z-index: 3;
77 | border-color: var(--#{$prefix}accordion-btn-focus-border-color);
78 | outline: 0;
79 | box-shadow: var(--#{$prefix}accordion-btn-focus-box-shadow);
80 | }
81 | }
82 |
83 | .accordion-header {
84 | margin-bottom: 0;
85 | }
86 |
87 | .accordion-item {
88 | color: var(--#{$prefix}accordion-color);
89 | background-color: var(--#{$prefix}accordion-bg);
90 | border: var(--#{$prefix}accordion-border-width) solid var(--#{$prefix}accordion-border-color);
91 |
92 | &:first-of-type {
93 | @include border-top-radius(var(--#{$prefix}accordion-border-radius));
94 |
95 | .accordion-button {
96 | @include border-top-radius(var(--#{$prefix}accordion-inner-border-radius));
97 | }
98 | }
99 |
100 | &:not(:first-of-type) {
101 | border-top: 0;
102 | }
103 |
104 | // Only set a border-radius on the last item if the accordion is collapsed
105 | &:last-of-type {
106 | @include border-bottom-radius(var(--#{$prefix}accordion-border-radius));
107 |
108 | .accordion-button {
109 | &.collapsed {
110 | @include border-bottom-radius(var(--#{$prefix}accordion-inner-border-radius));
111 | }
112 | }
113 |
114 | .accordion-collapse {
115 | @include border-bottom-radius(var(--#{$prefix}accordion-border-radius));
116 | }
117 | }
118 | }
119 |
120 | .accordion-body {
121 | padding: var(--#{$prefix}accordion-body-padding-y) var(--#{$prefix}accordion-body-padding-x);
122 | }
123 |
124 |
125 | // Flush accordion items
126 | //
127 | // Remove borders and border-radius to keep accordion items edge-to-edge.
128 |
129 | .accordion-flush {
130 | .accordion-collapse {
131 | border-width: 0;
132 | }
133 |
134 | .accordion-item {
135 | border-right: 0;
136 | border-left: 0;
137 | @include border-radius(0);
138 |
139 | &:first-child { border-top: 0; }
140 | &:last-child { border-bottom: 0; }
141 |
142 | .accordion-button {
143 | &,
144 | &.collapsed {
145 | @include border-radius(0);
146 | }
147 | }
148 | }
149 | }
150 |
151 | @if $enable-dark-mode {
152 | @include color-mode(dark) {
153 | .accordion-button::after {
154 | --#{$prefix}accordion-btn-icon: #{escape-svg($accordion-button-icon-dark)};
155 | --#{$prefix}accordion-btn-active-icon: #{escape-svg($accordion-button-active-icon-dark)};
156 | }
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/src/scss/bootstrap/_alert.scss:
--------------------------------------------------------------------------------
1 | //
2 | // Base styles
3 | //
4 |
5 | .alert {
6 | // scss-docs-start alert-css-vars
7 | --#{$prefix}alert-bg: transparent;
8 | --#{$prefix}alert-padding-x: #{$alert-padding-x};
9 | --#{$prefix}alert-padding-y: #{$alert-padding-y};
10 | --#{$prefix}alert-margin-bottom: #{$alert-margin-bottom};
11 | --#{$prefix}alert-color: inherit;
12 | --#{$prefix}alert-border-color: transparent;
13 | --#{$prefix}alert-border: #{$alert-border-width} solid var(--#{$prefix}alert-border-color);
14 | --#{$prefix}alert-border-radius: #{$alert-border-radius};
15 | --#{$prefix}alert-link-color: inherit;
16 | // scss-docs-end alert-css-vars
17 |
18 | position: relative;
19 | padding: var(--#{$prefix}alert-padding-y) var(--#{$prefix}alert-padding-x);
20 | margin-bottom: var(--#{$prefix}alert-margin-bottom);
21 | color: var(--#{$prefix}alert-color);
22 | background-color: var(--#{$prefix}alert-bg);
23 | border: var(--#{$prefix}alert-border);
24 | @include border-radius(var(--#{$prefix}alert-border-radius));
25 | }
26 |
27 | // Headings for larger alerts
28 | .alert-heading {
29 | // Specified to prevent conflicts of changing $headings-color
30 | color: inherit;
31 | }
32 |
33 | // Provide class for links that match alerts
34 | .alert-link {
35 | font-weight: $alert-link-font-weight;
36 | color: var(--#{$prefix}alert-link-color);
37 | }
38 |
39 |
40 | // Dismissible alerts
41 | //
42 | // Expand the right padding and account for the close button's positioning.
43 |
44 | .alert-dismissible {
45 | padding-right: $alert-dismissible-padding-r;
46 |
47 | // Adjust close link position
48 | .btn-close {
49 | position: absolute;
50 | top: 0;
51 | right: 0;
52 | z-index: $stretched-link-z-index + 1;
53 | padding: $alert-padding-y * 1.25 $alert-padding-x;
54 | }
55 | }
56 |
57 |
58 | // scss-docs-start alert-modifiers
59 | // Generate contextual modifier classes for colorizing the alert
60 | @each $state in map-keys($theme-colors) {
61 | .alert-#{$state} {
62 | --#{$prefix}alert-color: var(--#{$prefix}#{$state}-text);
63 | --#{$prefix}alert-bg: var(--#{$prefix}#{$state}-bg-subtle);
64 | --#{$prefix}alert-border-color: var(--#{$prefix}#{$state}-border-subtle);
65 | --#{$prefix}alert-link-color: var(--#{$prefix}#{$state}-text);
66 | }
67 | }
68 | // scss-docs-end alert-modifiers
69 |
--------------------------------------------------------------------------------
/src/scss/bootstrap/_badge.scss:
--------------------------------------------------------------------------------
1 | // Base class
2 | //
3 | // Requires one of the contextual, color modifier classes for `color` and
4 | // `background-color`.
5 |
6 | .badge {
7 | // scss-docs-start badge-css-vars
8 | --#{$prefix}badge-padding-x: #{$badge-padding-x};
9 | --#{$prefix}badge-padding-y: #{$badge-padding-y};
10 | @include rfs($badge-font-size, --#{$prefix}badge-font-size);
11 | --#{$prefix}badge-font-weight: #{$badge-font-weight};
12 | --#{$prefix}badge-color: #{$badge-color};
13 | --#{$prefix}badge-border-radius: #{$badge-border-radius};
14 | // scss-docs-end badge-css-vars
15 |
16 | display: inline-block;
17 | padding: var(--#{$prefix}badge-padding-y) var(--#{$prefix}badge-padding-x);
18 | @include font-size(var(--#{$prefix}badge-font-size));
19 | font-weight: var(--#{$prefix}badge-font-weight);
20 | line-height: 1;
21 | color: var(--#{$prefix}badge-color);
22 | text-align: center;
23 | white-space: nowrap;
24 | vertical-align: baseline;
25 | @include border-radius(var(--#{$prefix}badge-border-radius));
26 | @include gradient-bg();
27 |
28 | // Empty badges collapse automatically
29 | &:empty {
30 | display: none;
31 | }
32 | }
33 |
34 | // Quick fix for badges in buttons
35 | .btn .badge {
36 | position: relative;
37 | top: -1px;
38 | }
39 |
--------------------------------------------------------------------------------
/src/scss/bootstrap/_breadcrumb.scss:
--------------------------------------------------------------------------------
1 | .breadcrumb {
2 | // scss-docs-start breadcrumb-css-vars
3 | --#{$prefix}breadcrumb-padding-x: #{$breadcrumb-padding-x};
4 | --#{$prefix}breadcrumb-padding-y: #{$breadcrumb-padding-y};
5 | --#{$prefix}breadcrumb-margin-bottom: #{$breadcrumb-margin-bottom};
6 | @include rfs($breadcrumb-font-size, --#{$prefix}breadcrumb-font-size);
7 | --#{$prefix}breadcrumb-bg: #{$breadcrumb-bg};
8 | --#{$prefix}breadcrumb-border-radius: #{$breadcrumb-border-radius};
9 | --#{$prefix}breadcrumb-divider-color: #{$breadcrumb-divider-color};
10 | --#{$prefix}breadcrumb-item-padding-x: #{$breadcrumb-item-padding-x};
11 | --#{$prefix}breadcrumb-item-active-color: #{$breadcrumb-active-color};
12 | // scss-docs-end breadcrumb-css-vars
13 |
14 | display: flex;
15 | flex-wrap: wrap;
16 | padding: var(--#{$prefix}breadcrumb-padding-y) var(--#{$prefix}breadcrumb-padding-x);
17 | margin-bottom: var(--#{$prefix}breadcrumb-margin-bottom);
18 | @include font-size(var(--#{$prefix}breadcrumb-font-size));
19 | list-style: none;
20 | background-color: var(--#{$prefix}breadcrumb-bg);
21 | @include border-radius(var(--#{$prefix}breadcrumb-border-radius));
22 | }
23 |
24 | .breadcrumb-item {
25 | // The separator between breadcrumbs (by default, a forward-slash: "/")
26 | + .breadcrumb-item {
27 | padding-left: var(--#{$prefix}breadcrumb-item-padding-x);
28 |
29 | &::before {
30 | float: left; // Suppress inline spacings and underlining of the separator
31 | padding-right: var(--#{$prefix}breadcrumb-item-padding-x);
32 | color: var(--#{$prefix}breadcrumb-divider-color);
33 | content: var(--#{$prefix}breadcrumb-divider, escape-svg($breadcrumb-divider)) #{"/* rtl:"} var(--#{$prefix}breadcrumb-divider, escape-svg($breadcrumb-divider-flipped)) #{"*/"};
34 | }
35 | }
36 |
37 | &.active {
38 | color: var(--#{$prefix}breadcrumb-item-active-color);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/scss/bootstrap/_button-group.scss:
--------------------------------------------------------------------------------
1 | // Make the div behave like a button
2 | .btn-group,
3 | .btn-group-vertical {
4 | position: relative;
5 | display: inline-flex;
6 | vertical-align: middle; // match .btn alignment given font-size hack above
7 |
8 | > .btn {
9 | position: relative;
10 | flex: 1 1 auto;
11 | }
12 |
13 | // Bring the hover, focused, and "active" buttons to the front to overlay
14 | // the borders properly
15 | > .btn-check:checked + .btn,
16 | > .btn-check:focus + .btn,
17 | > .btn:hover,
18 | > .btn:focus,
19 | > .btn:active,
20 | > .btn.active {
21 | z-index: 1;
22 | }
23 | }
24 |
25 | // Optional: Group multiple button groups together for a toolbar
26 | .btn-toolbar {
27 | display: flex;
28 | flex-wrap: wrap;
29 | justify-content: flex-start;
30 |
31 | .input-group {
32 | width: auto;
33 | }
34 | }
35 |
36 | .btn-group {
37 | @include border-radius($btn-border-radius);
38 |
39 | // Prevent double borders when buttons are next to each other
40 | > :not(.btn-check:first-child) + .btn,
41 | > .btn-group:not(:first-child) {
42 | margin-left: calc($btn-border-width * -1); // stylelint-disable-line function-disallowed-list
43 | }
44 |
45 | // Reset rounded corners
46 | > .btn:not(:last-child):not(.dropdown-toggle),
47 | > .btn.dropdown-toggle-split:first-child,
48 | > .btn-group:not(:last-child) > .btn {
49 | @include border-end-radius(0);
50 | }
51 |
52 | // The left radius should be 0 if the button is:
53 | // - the "third or more" child
54 | // - the second child and the previous element isn't `.btn-check` (making it the first child visually)
55 | // - part of a btn-group which isn't the first child
56 | > .btn:nth-child(n + 3),
57 | > :not(.btn-check) + .btn,
58 | > .btn-group:not(:first-child) > .btn {
59 | @include border-start-radius(0);
60 | }
61 | }
62 |
63 | // Sizing
64 | //
65 | // Remix the default button sizing classes into new ones for easier manipulation.
66 |
67 | .btn-group-sm > .btn { @extend .btn-sm; }
68 | .btn-group-lg > .btn { @extend .btn-lg; }
69 |
70 |
71 | //
72 | // Split button dropdowns
73 | //
74 |
75 | .dropdown-toggle-split {
76 | padding-right: $btn-padding-x * .75;
77 | padding-left: $btn-padding-x * .75;
78 |
79 | &::after,
80 | .dropup &::after,
81 | .dropend &::after {
82 | margin-left: 0;
83 | }
84 |
85 | .dropstart &::before {
86 | margin-right: 0;
87 | }
88 | }
89 |
90 | .btn-sm + .dropdown-toggle-split {
91 | padding-right: $btn-padding-x-sm * .75;
92 | padding-left: $btn-padding-x-sm * .75;
93 | }
94 |
95 | .btn-lg + .dropdown-toggle-split {
96 | padding-right: $btn-padding-x-lg * .75;
97 | padding-left: $btn-padding-x-lg * .75;
98 | }
99 |
100 |
101 | // The clickable button for toggling the menu
102 | // Set the same inset shadow as the :active state
103 | .btn-group.show .dropdown-toggle {
104 | @include box-shadow($btn-active-box-shadow);
105 |
106 | // Show no shadow for `.btn-link` since it has no other button styles.
107 | &.btn-link {
108 | @include box-shadow(none);
109 | }
110 | }
111 |
112 |
113 | //
114 | // Vertical button groups
115 | //
116 |
117 | .btn-group-vertical {
118 | flex-direction: column;
119 | align-items: flex-start;
120 | justify-content: center;
121 |
122 | > .btn,
123 | > .btn-group {
124 | width: 100%;
125 | }
126 |
127 | > .btn:not(:first-child),
128 | > .btn-group:not(:first-child) {
129 | margin-top: calc($btn-border-width * -1); // stylelint-disable-line function-disallowed-list
130 | }
131 |
132 | // Reset rounded corners
133 | > .btn:not(:last-child):not(.dropdown-toggle),
134 | > .btn-group:not(:last-child) > .btn {
135 | @include border-bottom-radius(0);
136 | }
137 |
138 | > .btn ~ .btn,
139 | > .btn-group:not(:first-child) > .btn {
140 | @include border-top-radius(0);
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/src/scss/bootstrap/_buttons.scss:
--------------------------------------------------------------------------------
1 | //
2 | // Base styles
3 | //
4 |
5 | .btn {
6 | // scss-docs-start btn-css-vars
7 | --#{$prefix}btn-padding-x: #{$btn-padding-x};
8 | --#{$prefix}btn-padding-y: #{$btn-padding-y};
9 | --#{$prefix}btn-font-family: #{$btn-font-family};
10 | @include rfs($btn-font-size, --#{$prefix}btn-font-size);
11 | --#{$prefix}btn-font-weight: #{$btn-font-weight};
12 | --#{$prefix}btn-line-height: #{$btn-line-height};
13 | --#{$prefix}btn-color: #{$body-color};
14 | --#{$prefix}btn-bg: transparent;
15 | --#{$prefix}btn-border-width: #{$btn-border-width};
16 | --#{$prefix}btn-border-color: transparent;
17 | --#{$prefix}btn-border-radius: #{$btn-border-radius};
18 | --#{$prefix}btn-hover-border-color: transparent;
19 | --#{$prefix}btn-box-shadow: #{$btn-box-shadow};
20 | --#{$prefix}btn-disabled-opacity: #{$btn-disabled-opacity};
21 | --#{$prefix}btn-focus-box-shadow: 0 0 0 #{$btn-focus-width} rgba(var(--#{$prefix}btn-focus-shadow-rgb), .5);
22 | // scss-docs-end btn-css-vars
23 |
24 | display: inline-block;
25 | padding: var(--#{$prefix}btn-padding-y) var(--#{$prefix}btn-padding-x);
26 | font-family: var(--#{$prefix}btn-font-family);
27 | @include font-size(var(--#{$prefix}btn-font-size));
28 | font-weight: var(--#{$prefix}btn-font-weight);
29 | line-height: var(--#{$prefix}btn-line-height);
30 | color: var(--#{$prefix}btn-color);
31 | text-align: center;
32 | text-decoration: if($link-decoration == none, null, none);
33 | white-space: $btn-white-space;
34 | vertical-align: middle;
35 | cursor: if($enable-button-pointers, pointer, null);
36 | user-select: none;
37 | border: var(--#{$prefix}btn-border-width) solid var(--#{$prefix}btn-border-color);
38 | @include border-radius(var(--#{$prefix}btn-border-radius));
39 | @include gradient-bg(var(--#{$prefix}btn-bg));
40 | @include box-shadow(var(--#{$prefix}btn-box-shadow));
41 | @include transition($btn-transition);
42 |
43 | &:hover {
44 | color: var(--#{$prefix}btn-hover-color);
45 | text-decoration: if($link-hover-decoration == underline, none, null);
46 | background-color: var(--#{$prefix}btn-hover-bg);
47 | border-color: var(--#{$prefix}btn-hover-border-color);
48 | }
49 |
50 | .btn-check + &:hover {
51 | // override for the checkbox/radio buttons
52 | color: var(--#{$prefix}btn-color);
53 | background-color: var(--#{$prefix}btn-bg);
54 | border-color: var(--#{$prefix}btn-border-color);
55 | }
56 |
57 | &:focus-visible {
58 | color: var(--#{$prefix}btn-hover-color);
59 | @include gradient-bg(var(--#{$prefix}btn-hover-bg));
60 | border-color: var(--#{$prefix}btn-hover-border-color);
61 | outline: 0;
62 | // Avoid using mixin so we can pass custom focus shadow properly
63 | @if $enable-shadows {
64 | box-shadow: var(--#{$prefix}btn-box-shadow), var(--#{$prefix}btn-focus-box-shadow);
65 | } @else {
66 | box-shadow: var(--#{$prefix}btn-focus-box-shadow);
67 | }
68 | }
69 |
70 | .btn-check:focus-visible + & {
71 | border-color: var(--#{$prefix}btn-hover-border-color);
72 | outline: 0;
73 | // Avoid using mixin so we can pass custom focus shadow properly
74 | @if $enable-shadows {
75 | box-shadow: var(--#{$prefix}btn-box-shadow), var(--#{$prefix}btn-focus-box-shadow);
76 | } @else {
77 | box-shadow: var(--#{$prefix}btn-focus-box-shadow);
78 | }
79 | }
80 |
81 | .btn-check:checked + &,
82 | :not(.btn-check) + &:active,
83 | &:first-child:active,
84 | &.active,
85 | &.show {
86 | color: var(--#{$prefix}btn-active-color);
87 | background-color: var(--#{$prefix}btn-active-bg);
88 | // Remove CSS gradients if they're enabled
89 | background-image: if($enable-gradients, none, null);
90 | border-color: var(--#{$prefix}btn-active-border-color);
91 | @include box-shadow(var(--#{$prefix}btn-active-shadow));
92 |
93 | &:focus-visible {
94 | // Avoid using mixin so we can pass custom focus shadow properly
95 | @if $enable-shadows {
96 | box-shadow: var(--#{$prefix}btn-active-shadow), var(--#{$prefix}btn-focus-box-shadow);
97 | } @else {
98 | box-shadow: var(--#{$prefix}btn-focus-box-shadow);
99 | }
100 | }
101 | }
102 |
103 | &:disabled,
104 | &.disabled,
105 | fieldset:disabled & {
106 | color: var(--#{$prefix}btn-disabled-color);
107 | pointer-events: none;
108 | background-color: var(--#{$prefix}btn-disabled-bg);
109 | background-image: if($enable-gradients, none, null);
110 | border-color: var(--#{$prefix}btn-disabled-border-color);
111 | opacity: var(--#{$prefix}btn-disabled-opacity);
112 | @include box-shadow(none);
113 | }
114 | }
115 |
116 |
117 | //
118 | // Alternate buttons
119 | //
120 |
121 | // scss-docs-start btn-variant-loops
122 | @each $color, $value in $theme-colors {
123 | .btn-#{$color} {
124 | @if $color == "light" {
125 | @include button-variant(
126 | $value,
127 | $value,
128 | $hover-background: shade-color($value, $btn-hover-bg-shade-amount),
129 | $hover-border: shade-color($value, $btn-hover-border-shade-amount),
130 | $active-background: shade-color($value, $btn-active-bg-shade-amount),
131 | $active-border: shade-color($value, $btn-active-border-shade-amount)
132 | );
133 | } @else if $color == "dark" {
134 | @include button-variant(
135 | $value,
136 | $value,
137 | $hover-background: tint-color($value, $btn-hover-bg-tint-amount),
138 | $hover-border: tint-color($value, $btn-hover-border-tint-amount),
139 | $active-background: tint-color($value, $btn-active-bg-tint-amount),
140 | $active-border: tint-color($value, $btn-active-border-tint-amount)
141 | );
142 | } @else {
143 | @include button-variant($value, $value);
144 | }
145 | }
146 | }
147 |
148 | @each $color, $value in $theme-colors {
149 | .btn-outline-#{$color} {
150 | @include button-outline-variant($value);
151 | }
152 | }
153 | // scss-docs-end btn-variant-loops
154 |
155 |
156 | //
157 | // Link buttons
158 | //
159 |
160 | // Make a button look and behave like a link
161 | .btn-link {
162 | --#{$prefix}btn-font-weight: #{$font-weight-normal};
163 | --#{$prefix}btn-color: #{$btn-link-color};
164 | --#{$prefix}btn-bg: transparent;
165 | --#{$prefix}btn-border-color: transparent;
166 | --#{$prefix}btn-hover-color: #{$btn-link-hover-color};
167 | --#{$prefix}btn-hover-border-color: transparent;
168 | --#{$prefix}btn-active-color: #{$btn-link-hover-color};
169 | --#{$prefix}btn-active-border-color: transparent;
170 | --#{$prefix}btn-disabled-color: #{$btn-link-disabled-color};
171 | --#{$prefix}btn-disabled-border-color: transparent;
172 | --#{$prefix}btn-box-shadow: none;
173 | --#{$prefix}btn-focus-shadow-rgb: #{to-rgb(mix(color-contrast($primary), $primary, 15%))};
174 |
175 | text-decoration: $link-decoration;
176 | @if $enable-gradients {
177 | background-image: none;
178 | }
179 |
180 | &:hover,
181 | &:focus-visible {
182 | text-decoration: $link-hover-decoration;
183 | }
184 |
185 | &:focus-visible {
186 | color: var(--#{$prefix}btn-color);
187 | }
188 |
189 | &:hover {
190 | color: var(--#{$prefix}btn-hover-color);
191 | }
192 |
193 | // No need for an active state here
194 | }
195 |
196 |
197 | //
198 | // Button Sizes
199 | //
200 |
201 | .btn-lg {
202 | @include button-size($btn-padding-y-lg, $btn-padding-x-lg, $btn-font-size-lg, $btn-border-radius-lg);
203 | }
204 |
205 | .btn-sm {
206 | @include button-size($btn-padding-y-sm, $btn-padding-x-sm, $btn-font-size-sm, $btn-border-radius-sm);
207 | }
208 |
--------------------------------------------------------------------------------
/src/scss/bootstrap/_carousel.scss:
--------------------------------------------------------------------------------
1 | // Notes on the classes:
2 | //
3 | // 1. .carousel.pointer-event should ideally be pan-y (to allow for users to scroll vertically)
4 | // even when their scroll action started on a carousel, but for compatibility (with Firefox)
5 | // we're preventing all actions instead
6 | // 2. The .carousel-item-start and .carousel-item-end is used to indicate where
7 | // the active slide is heading.
8 | // 3. .active.carousel-item is the current slide.
9 | // 4. .active.carousel-item-start and .active.carousel-item-end is the current
10 | // slide in its in-transition state. Only one of these occurs at a time.
11 | // 5. .carousel-item-next.carousel-item-start and .carousel-item-prev.carousel-item-end
12 | // is the upcoming slide in transition.
13 |
14 | .carousel {
15 | position: relative;
16 | }
17 |
18 | .carousel.pointer-event {
19 | touch-action: pan-y;
20 | }
21 |
22 | .carousel-inner {
23 | position: relative;
24 | width: 100%;
25 | overflow: hidden;
26 | @include clearfix();
27 | }
28 |
29 | .carousel-item {
30 | position: relative;
31 | display: none;
32 | float: left;
33 | width: 100%;
34 | margin-right: -100%;
35 | backface-visibility: hidden;
36 | @include transition($carousel-transition);
37 | }
38 |
39 | .carousel-item.active,
40 | .carousel-item-next,
41 | .carousel-item-prev {
42 | display: block;
43 | }
44 |
45 | .carousel-item-next:not(.carousel-item-start),
46 | .active.carousel-item-end {
47 | transform: translateX(100%);
48 | }
49 |
50 | .carousel-item-prev:not(.carousel-item-end),
51 | .active.carousel-item-start {
52 | transform: translateX(-100%);
53 | }
54 |
55 |
56 | //
57 | // Alternate transitions
58 | //
59 |
60 | .carousel-fade {
61 | .carousel-item {
62 | opacity: 0;
63 | transition-property: opacity;
64 | transform: none;
65 | }
66 |
67 | .carousel-item.active,
68 | .carousel-item-next.carousel-item-start,
69 | .carousel-item-prev.carousel-item-end {
70 | z-index: 1;
71 | opacity: 1;
72 | }
73 |
74 | .active.carousel-item-start,
75 | .active.carousel-item-end {
76 | z-index: 0;
77 | opacity: 0;
78 | @include transition(opacity 0s $carousel-transition-duration);
79 | }
80 | }
81 |
82 |
83 | //
84 | // Left/right controls for nav
85 | //
86 |
87 | .carousel-control-prev,
88 | .carousel-control-next {
89 | position: absolute;
90 | top: 0;
91 | bottom: 0;
92 | z-index: 1;
93 | // Use flex for alignment (1-3)
94 | display: flex; // 1. allow flex styles
95 | align-items: center; // 2. vertically center contents
96 | justify-content: center; // 3. horizontally center contents
97 | width: $carousel-control-width;
98 | padding: 0;
99 | color: $carousel-control-color;
100 | text-align: center;
101 | background: none;
102 | border: 0;
103 | opacity: $carousel-control-opacity;
104 | @include transition($carousel-control-transition);
105 |
106 | // Hover/focus state
107 | &:hover,
108 | &:focus {
109 | color: $carousel-control-color;
110 | text-decoration: none;
111 | outline: 0;
112 | opacity: $carousel-control-hover-opacity;
113 | }
114 | }
115 | .carousel-control-prev {
116 | left: 0;
117 | background-image: if($enable-gradients, linear-gradient(90deg, rgba($black, .25), rgba($black, .001)), null);
118 | }
119 | .carousel-control-next {
120 | right: 0;
121 | background-image: if($enable-gradients, linear-gradient(270deg, rgba($black, .25), rgba($black, .001)), null);
122 | }
123 |
124 | // Icons for within
125 | .carousel-control-prev-icon,
126 | .carousel-control-next-icon {
127 | display: inline-block;
128 | width: $carousel-control-icon-width;
129 | height: $carousel-control-icon-width;
130 | background-repeat: no-repeat;
131 | background-position: 50%;
132 | background-size: 100% 100%;
133 | }
134 |
135 | /* rtl:options: {
136 | "autoRename": true,
137 | "stringMap":[ {
138 | "name" : "prev-next",
139 | "search" : "prev",
140 | "replace" : "next"
141 | } ]
142 | } */
143 | .carousel-control-prev-icon {
144 | background-image: escape-svg($carousel-control-prev-icon-bg);
145 | }
146 | .carousel-control-next-icon {
147 | background-image: escape-svg($carousel-control-next-icon-bg);
148 | }
149 |
150 | // Optional indicator pips/controls
151 | //
152 | // Add a container (such as a list) with the following class and add an item (ideally a focusable control,
153 | // like a button) with data-bs-target for each slide your carousel holds.
154 |
155 | .carousel-indicators {
156 | position: absolute;
157 | right: 0;
158 | bottom: 0;
159 | left: 0;
160 | z-index: 2;
161 | display: flex;
162 | justify-content: center;
163 | padding: 0;
164 | // Use the .carousel-control's width as margin so we don't overlay those
165 | margin-right: $carousel-control-width;
166 | margin-bottom: 1rem;
167 | margin-left: $carousel-control-width;
168 | list-style: none;
169 |
170 | [data-bs-target] {
171 | box-sizing: content-box;
172 | flex: 0 1 auto;
173 | width: $carousel-indicator-width;
174 | height: $carousel-indicator-height;
175 | padding: 0;
176 | margin-right: $carousel-indicator-spacer;
177 | margin-left: $carousel-indicator-spacer;
178 | text-indent: -999px;
179 | cursor: pointer;
180 | background-color: $carousel-indicator-active-bg;
181 | background-clip: padding-box;
182 | border: 0;
183 | // Use transparent borders to increase the hit area by 10px on top and bottom.
184 | border-top: $carousel-indicator-hit-area-height solid transparent;
185 | border-bottom: $carousel-indicator-hit-area-height solid transparent;
186 | opacity: $carousel-indicator-opacity;
187 | @include transition($carousel-indicator-transition);
188 | }
189 |
190 | .active {
191 | opacity: $carousel-indicator-active-opacity;
192 | }
193 | }
194 |
195 |
196 | // Optional captions
197 | //
198 | //
199 |
200 | .carousel-caption {
201 | position: absolute;
202 | right: (100% - $carousel-caption-width) * .5;
203 | bottom: $carousel-caption-spacer;
204 | left: (100% - $carousel-caption-width) * .5;
205 | padding-top: $carousel-caption-padding-y;
206 | padding-bottom: $carousel-caption-padding-y;
207 | color: $carousel-caption-color;
208 | text-align: center;
209 | }
210 |
211 | // Dark mode carousel
212 |
213 | @mixin carousel-dark() {
214 | .carousel-control-prev-icon,
215 | .carousel-control-next-icon {
216 | filter: $carousel-dark-control-icon-filter;
217 | }
218 |
219 | .carousel-indicators [data-bs-target] {
220 | background-color: $carousel-dark-indicator-active-bg;
221 | }
222 |
223 | .carousel-caption {
224 | color: $carousel-dark-caption-color;
225 | }
226 | }
227 |
228 | .carousel-dark {
229 | @include carousel-dark();
230 | }
231 |
232 | @if $enable-dark-mode {
233 | @include color-mode(dark) {
234 | .carousel {
235 | @include carousel-dark();
236 | }
237 | }
238 | }
239 |
--------------------------------------------------------------------------------
/src/scss/bootstrap/_close.scss:
--------------------------------------------------------------------------------
1 | // Transparent background and border properties included for button version.
2 | // iOS requires the button element instead of an anchor tag.
3 | // If you want the anchor version, it requires `href="#"`.
4 | // See https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile
5 |
6 | .btn-close {
7 | --#{$prefix}btn-close-color: #{$btn-close-color};
8 | --#{$prefix}btn-close-bg: #{ escape-svg($btn-close-bg) };
9 | --#{$prefix}btn-close-opacity: #{$btn-close-opacity};
10 | --#{$prefix}btn-close-hover-opacity: #{$btn-close-hover-opacity};
11 | --#{$prefix}btn-close-focus-shadow: #{$btn-close-focus-shadow};
12 | --#{$prefix}btn-close-focus-opacity: #{$btn-close-focus-opacity};
13 | --#{$prefix}btn-close-disabled-opacity: #{$btn-close-disabled-opacity};
14 | --#{$prefix}btn-close-white-filter: #{$btn-close-white-filter};
15 |
16 | box-sizing: content-box;
17 | width: $btn-close-width;
18 | height: $btn-close-height;
19 | padding: $btn-close-padding-y $btn-close-padding-x;
20 | color: var(--#{$prefix}btn-close-color);
21 | background: transparent var(--#{$prefix}btn-close-bg) center / $btn-close-width auto no-repeat; // include transparent for button elements
22 | border: 0; // for button elements
23 | @include border-radius();
24 | opacity: var(--#{$prefix}btn-close-opacity);
25 |
26 | // Override 's hover style
27 | &:hover {
28 | color: var(--#{$prefix}btn-close-color);
29 | text-decoration: none;
30 | opacity: var(--#{$prefix}btn-close-hover-opacity);
31 | }
32 |
33 | &:focus {
34 | outline: 0;
35 | box-shadow: var(--#{$prefix}btn-close-focus-shadow);
36 | opacity: var(--#{$prefix}btn-close-focus-opacity);
37 | }
38 |
39 | &:disabled,
40 | &.disabled {
41 | pointer-events: none;
42 | user-select: none;
43 | opacity: var(--#{$prefix}btn-close-disabled-opacity);
44 | }
45 | }
46 |
47 | @mixin btn-close-white() {
48 | filter: var(--#{$prefix}btn-close-white-filter);
49 | }
50 |
51 | .btn-close-white {
52 | @include btn-close-white();
53 | }
54 |
55 | @if $enable-dark-mode {
56 | @include color-mode(dark) {
57 | .btn-close {
58 | @include btn-close-white();
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/scss/bootstrap/_containers.scss:
--------------------------------------------------------------------------------
1 | // Container widths
2 | //
3 | // Set the container width, and override it for fixed navbars in media queries.
4 |
5 | @if $enable-container-classes {
6 | // Single container class with breakpoint max-widths
7 | .container,
8 | // 100% wide container at all breakpoints
9 | .container-fluid {
10 | @include make-container();
11 | }
12 |
13 | // Responsive containers that are 100% wide until a breakpoint
14 | @each $breakpoint, $container-max-width in $container-max-widths {
15 | .container-#{$breakpoint} {
16 | @extend .container-fluid;
17 | }
18 |
19 | @include media-breakpoint-up($breakpoint, $grid-breakpoints) {
20 | %responsive-container-#{$breakpoint} {
21 | max-width: $container-max-width;
22 | }
23 |
24 | // Extend each breakpoint which is smaller or equal to the current breakpoint
25 | $extend-breakpoint: true;
26 |
27 | @each $name, $width in $grid-breakpoints {
28 | @if ($extend-breakpoint) {
29 | .container#{breakpoint-infix($name, $grid-breakpoints)} {
30 | @extend %responsive-container-#{$breakpoint};
31 | }
32 |
33 | // Once the current breakpoint is reached, stop extending
34 | @if ($breakpoint == $name) {
35 | $extend-breakpoint: false;
36 | }
37 | }
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/scss/bootstrap/_forms.scss:
--------------------------------------------------------------------------------
1 | @import "forms/labels";
2 | @import "forms/form-text";
3 | @import "forms/form-control";
4 | @import "forms/form-select";
5 | @import "forms/form-check";
6 | @import "forms/form-range";
7 | @import "forms/floating-labels";
8 | @import "forms/input-group";
9 | @import "forms/validation";
10 |
--------------------------------------------------------------------------------
/src/scss/bootstrap/_grid.scss:
--------------------------------------------------------------------------------
1 | // Row
2 | //
3 | // Rows contain your columns.
4 |
5 | @if $enable-grid-classes {
6 | .row {
7 | @include make-row();
8 |
9 | > * {
10 | @include make-col-ready();
11 | }
12 | }
13 | }
14 |
15 | @if $enable-cssgrid {
16 | .grid {
17 | display: grid;
18 | grid-template-rows: repeat(var(--#{$prefix}rows, 1), 1fr);
19 | grid-template-columns: repeat(var(--#{$prefix}columns, #{$grid-columns}), 1fr);
20 | gap: var(--#{$prefix}gap, #{$grid-gutter-width});
21 |
22 | @include make-cssgrid();
23 | }
24 | }
25 |
26 |
27 | // Columns
28 | //
29 | // Common styles for small and large grid columns
30 |
31 | @if $enable-grid-classes {
32 | @include make-grid-columns();
33 | }
34 |
--------------------------------------------------------------------------------
/src/scss/bootstrap/_helpers.scss:
--------------------------------------------------------------------------------
1 | @import "helpers/clearfix";
2 | @import "helpers/color-bg";
3 | @import "helpers/colored-links";
4 | @import "helpers/ratio";
5 | @import "helpers/position";
6 | @import "helpers/stacks";
7 | @import "helpers/visually-hidden";
8 | @import "helpers/stretched-link";
9 | @import "helpers/text-truncation";
10 | @import "helpers/vr";
11 |
--------------------------------------------------------------------------------
/src/scss/bootstrap/_images.scss:
--------------------------------------------------------------------------------
1 | // Responsive images (ensure images don't scale beyond their parents)
2 | //
3 | // This is purposefully opt-in via an explicit class rather than being the default for all `
`s.
4 | // We previously tried the "images are responsive by default" approach in Bootstrap v2,
5 | // and abandoned it in Bootstrap v3 because it breaks lots of third-party widgets (including Google Maps)
6 | // which weren't expecting the images within themselves to be involuntarily resized.
7 | // See also https://github.com/twbs/bootstrap/issues/18178
8 | .img-fluid {
9 | @include img-fluid();
10 | }
11 |
12 |
13 | // Image thumbnails
14 | .img-thumbnail {
15 | padding: $thumbnail-padding;
16 | background-color: $thumbnail-bg;
17 | border: $thumbnail-border-width solid $thumbnail-border-color;
18 | @include border-radius($thumbnail-border-radius);
19 | @include box-shadow($thumbnail-box-shadow);
20 |
21 | // Keep them at most 100% wide
22 | @include img-fluid();
23 | }
24 |
25 | //
26 | // Figures
27 | //
28 |
29 | .figure {
30 | // Ensures the caption's text aligns with the image.
31 | display: inline-block;
32 | }
33 |
34 | .figure-img {
35 | margin-bottom: $spacer * .5;
36 | line-height: 1;
37 | }
38 |
39 | .figure-caption {
40 | @include font-size($figure-caption-font-size);
41 | color: $figure-caption-color;
42 | }
43 |
--------------------------------------------------------------------------------
/src/scss/bootstrap/_maps.scss:
--------------------------------------------------------------------------------
1 | // Re-assigned maps
2 | //
3 | // Placed here so that others can override the default Sass maps and see automatic updates to utilities and more.
4 |
5 | // scss-docs-start theme-colors-rgb
6 | $theme-colors-rgb: map-loop($theme-colors, to-rgb, "$value") !default;
7 | // scss-docs-end theme-colors-rgb
8 |
9 | $theme-colors-text: (
10 | "primary": $primary-text,
11 | "secondary": $secondary-text,
12 | "success": $success-text,
13 | "info": $info-text,
14 | "warning": $warning-text,
15 | "danger": $danger-text,
16 | "light": $light-text,
17 | "dark": $dark-text,
18 | ) !default;
19 |
20 | $theme-colors-bg-subtle: (
21 | "primary": $primary-bg-subtle,
22 | "secondary": $secondary-bg-subtle,
23 | "success": $success-bg-subtle,
24 | "info": $info-bg-subtle,
25 | "warning": $warning-bg-subtle,
26 | "danger": $danger-bg-subtle,
27 | "light": $light-bg-subtle,
28 | "dark": $dark-bg-subtle,
29 | ) !default;
30 |
31 | $theme-colors-border-subtle: (
32 | "primary": $primary-border-subtle,
33 | "secondary": $secondary-border-subtle,
34 | "success": $success-border-subtle,
35 | "info": $info-border-subtle,
36 | "warning": $warning-border-subtle,
37 | "danger": $danger-border-subtle,
38 | "light": $light-border-subtle,
39 | "dark": $dark-border-subtle,
40 | ) !default;
41 |
42 | // Utilities maps
43 | //
44 | // Extends the default `$theme-colors` maps to help create our utilities.
45 |
46 | // Come v6, we'll de-dupe these variables. Until then, for backward compatibility, we keep them to reassign.
47 | // scss-docs-start utilities-colors
48 | $utilities-colors: $theme-colors-rgb !default;
49 | // scss-docs-end utilities-colors
50 |
51 | // scss-docs-start utilities-text-colors
52 | $utilities-text: map-merge(
53 | $utilities-colors,
54 | (
55 | "black": to-rgb($black),
56 | "white": to-rgb($white),
57 | "body": to-rgb($body-color)
58 | )
59 | ) !default;
60 | $utilities-text-colors: map-loop($utilities-text, rgba-css-var, "$key", "text") !default;
61 |
62 | $utilities-text-emphasis-colors: (
63 | "primary-emphasis": var(--#{$prefix}primary-text),
64 | "secondary-emphasis": var(--#{$prefix}secondary-text),
65 | "success-emphasis": var(--#{$prefix}success-text),
66 | "info-emphasis": var(--#{$prefix}info-text),
67 | "warning-emphasis": var(--#{$prefix}warning-text),
68 | "danger-emphasis": var(--#{$prefix}danger-text),
69 | "light-emphasis": var(--#{$prefix}light-text),
70 | "dark-emphasis": var(--#{$prefix}dark-text)
71 | ) !default;
72 | // scss-docs-end utilities-text-colors
73 |
74 | // scss-docs-start utilities-bg-colors
75 | $utilities-bg: map-merge(
76 | $utilities-colors,
77 | (
78 | "black": to-rgb($black),
79 | "white": to-rgb($white),
80 | "body": to-rgb($body-bg)
81 | )
82 | ) !default;
83 | $utilities-bg-colors: map-loop($utilities-bg, rgba-css-var, "$key", "bg") !default;
84 |
85 | $utilities-bg-subtle: (
86 | "primary-subtle": var(--#{$prefix}primary-bg-subtle),
87 | "secondary-subtle": var(--#{$prefix}secondary-bg-subtle),
88 | "success-subtle": var(--#{$prefix}success-bg-subtle),
89 | "info-subtle": var(--#{$prefix}info-bg-subtle),
90 | "warning-subtle": var(--#{$prefix}warning-bg-subtle),
91 | "danger-subtle": var(--#{$prefix}danger-bg-subtle),
92 | "light-subtle": var(--#{$prefix}light-bg-subtle),
93 | "dark-subtle": var(--#{$prefix}dark-bg-subtle)
94 | ) !default;
95 | // $utilities-bg-subtle-colors: map-loop($utilities-bg-subtle, rgba-css-var, "$key", "bg") !default;
96 | // scss-docs-end utilities-bg-colors
97 |
98 | // scss-docs-start utilities-border-colors
99 | $utilities-border: map-merge(
100 | $utilities-colors,
101 | (
102 | "white": to-rgb($white)
103 | )
104 | ) !default;
105 | $utilities-border-colors: map-loop($utilities-border, rgba-css-var, "$key", "border") !default;
106 |
107 | $utilities-border-subtle: (
108 | "primary-subtle": var(--#{$prefix}primary-border-subtle),
109 | "secondary-subtle": var(--#{$prefix}secondary-border-subtle),
110 | "success-subtle": var(--#{$prefix}success-border-subtle),
111 | "info-subtle": var(--#{$prefix}info-border-subtle),
112 | "warning-subtle": var(--#{$prefix}warning-border-subtle),
113 | "danger-subtle": var(--#{$prefix}danger-border-subtle),
114 | "light-subtle": var(--#{$prefix}light-border-subtle),
115 | "dark-subtle": var(--#{$prefix}dark-border-subtle)
116 | ) !default;
117 | // scss-docs-end utilities-border-colors
118 |
119 | $negative-spacers: if($enable-negative-margins, negativify-map($spacers), null) !default;
120 |
121 | $gutters: $spacers !default;
122 |
--------------------------------------------------------------------------------
/src/scss/bootstrap/_mixins.scss:
--------------------------------------------------------------------------------
1 | // Toggles
2 | //
3 | // Used in conjunction with global variables to enable certain theme features.
4 |
5 | // Vendor
6 | @import "vendor/rfs";
7 |
8 | // Deprecate
9 | @import "mixins/deprecate";
10 |
11 | // Helpers
12 | @import "mixins/breakpoints";
13 | @import "mixins/color-mode";
14 | @import "mixins/color-scheme";
15 | @import "mixins/image";
16 | @import "mixins/resize";
17 | @import "mixins/visually-hidden";
18 | @import "mixins/reset-text";
19 | @import "mixins/text-truncate";
20 |
21 | // Utilities
22 | @import "mixins/utilities";
23 |
24 | // Components
25 | @import "mixins/backdrop";
26 | @import "mixins/buttons";
27 | @import "mixins/caret";
28 | @import "mixins/pagination";
29 | @import "mixins/lists";
30 | @import "mixins/forms";
31 | @import "mixins/table-variants";
32 |
33 | // Skins
34 | @import "mixins/border-radius";
35 | @import "mixins/box-shadow";
36 | @import "mixins/gradients";
37 | @import "mixins/transition";
38 |
39 | // Layout
40 | @import "mixins/clearfix";
41 | @import "mixins/container";
42 | @import "mixins/grid";
43 |
--------------------------------------------------------------------------------
/src/scss/bootstrap/_nav.scss:
--------------------------------------------------------------------------------
1 | // Base class
2 | //
3 | // Kickstart any navigation component with a set of style resets. Works with
4 | // `