Sunday, 24 May 2026

Windows Download sorter batch file

 @echo off

title Downloads Sorter
setlocal enabledelayedexpansion


goto top

:reset
if "%1"=="" (cls & echo You cannot goto :reset, use 'call :reset *random letter*' & pause)
set compressedToggle=
set videoToggle=
set audioToggle=
set imageToggle=
set documentsToggle=
set exeToggle=
set tdToggle=
goto :eof

:top

call :reset f
cls
echo.
echo Check where download folder is;
SET username22=akendrick451
:: =============================================
:: Configuration
:: =============================================
:: Folders to exclude (case-insensitive)
set "Excluded=Documents Videos Images Other Compressed Audio Images Exe 3D"

if exist "E:\Users\%username22%\Downloads\" (
    SET DownloadFolder=E:\Users\%username22%\Downloads
) else  (
    SET DownloadFolder=C:\Users\akend\Downloads
)
set OtherFolder=%DownloadFolder%\Other

echo Got downlod folder at location...
echo %DownloadFolder%
pause
goto types

 
:types
set compressedToggle=X
set videoToggle=X
set audioToggle=X
set imageToggle=X
set documentsToggle=X
set exeToggle=X
set tdToggle=X
set foldersToggle=X


goto runScan


:runScan
cls
if "%compressedToggle%"=="X" call :sortCompressed
if "%videoToggle%"=="X" call :sortVideo
if "%audioToggle%"=="X" call :sortAudio
if "%imageToggle%"=="X" call :sortImage
if "%documentsToggle%"=="X" call :sortDocuments
if "%exeToggle%"=="X" call :sortExe
if "%tdToggle%"=="X" call :sortTD
call :sortOther

goto done

:sortCompressed
echo Sorting Compressed...
if not exist "%DownloadFolder%Compressed" md "%DownloadFolder%Compressed" >nul
echo  compressed 1
for %%A in ("%DownloadFolder%*.rar") do move "%%A" "%DownloadFolder%Compressed\" >nul
echo  compressed 2
for %%A in ("%DownloadFolder%*.7z") do move "%%A" "%DownloadFolder%Compressed\" >nul
echo  compressed 3
for %%A in ("%DownloadFolder%*.zip") do move "%%A" "%DownloadFolder%Compressed\" >nul
echo ("%DownloadFolder%*.zip")
echo  compressed 4
goto :eof

:sortVideo
REM 3gp, f4v, flv, h264, mov, mp4, mpeg, mpg, webm

echo Sorting Videos...
if not exist "%DownloadFolder%Videos" md "%DownloadFolder%Videos" >nul
for %%A in ("%DownloadFolder%*.3gp") do move "%%A" "%DownloadFolder%Videos\" >nul
for %%A in ("%DownloadFolder%*.f4v") do move "%%A" "%DownloadFolder%Videos\" >nul
for %%A in ("%DownloadFolder%*.flv") do move "%%A" "%DownloadFolder%Videos\" >nul
for %%A in ("%DownloadFolder%*.h264") do move "%%A" "%DownloadFolder%Videos\" >nul
for %%A in ("%DownloadFolder%*.mov") do move "%%A" "%DownloadFolder%Videos\" >nul
for %%A in ("%DownloadFolder%*.mp4") do move "%%A" "%DownloadFolder%Videos\" >nul
for %%A in ("%DownloadFolder%*.mpeg") do move "%%A" "%DownloadFolder%Videos\" >nul
for %%A in ("%DownloadFolder%*.mpg") do move "%%A" "%DownloadFolder%Videos\" >nul
for %%A in ("%DownloadFolder%*.webm") do move "%%A" "%DownloadFolder%Videos\" >nul
goto :eof

:sortAudio
REM (mp3, wav, m4a, ogg, wma)

echo Sorting Audio...
if not exist "%DownloadFolder%Audio\" md "%DownloadFolder%Audio\" >nul
for %%A in ("%DownloadFolder%*.mp3") do move "%%A" "%DownloadFolder%Audio\" >nul
for %%A in ("%DownloadFolder%*.wav") do move "%%A" "%DownloadFolder%Audio\" >nul
for %%A in ("%DownloadFolder%*.m4a") do move "%%A" "%DownloadFolder%Audio\" >nul
for %%A in ("%DownloadFolder%*.ogg") do move "%%A" "%DownloadFolder%Audio\" >nul
for %%A in ("%DownloadFolder%*.wma") do move "%%A" "%DownloadFolder%Audio\" >nul
goto :eof

:sortImage
REM (bmp, ico, jpeg, jpg, png, tga, tif, tiff)

echo Sorting Images...
if not exist "%DownloadFolder%Images\" md "%DownloadFolder%Images\" >nul
for %%A in ("%DownloadFolder%*.bmp") do move "%%A" "%DownloadFolder%Images\" >nul
for %%A in ("%DownloadFolder%*.ico") do move "%%A" "%DownloadFolder%Images\" >nul
for %%A in ("%DownloadFolder%*.jpeg") do move "%%A" "%DownloadFolder%Images\" >nul
for %%A in ("%DownloadFolder%*.jpg") do move "%%A" "%DownloadFolder%Images\" >nul
for %%A in ("%DownloadFolder%*.png") do move "%%A" "%DownloadFolder%Images\" >nul
for %%A in ("%DownloadFolder%*.tga") do move "%%A" "%DownloadFolder%Images\" >nul
for %%A in ("%DownloadFolder%*.tif") do move "%%A" "%DownloadFolder%Images\" >nul
for %%A in ("%DownloadFolder%*.tiff") do move "%%A" "%DownloadFolder%Images\" >nul
goto :eof

:sortDocuments
REM doc, docm, docx, pdf, rtf, xls, xml, txt

echo Sorting Documents...
if not exist "%DownloadFolder%Documents\" md "%DownloadFolder%Documents\" >nul
for %%A in ("%DownloadFolder%*.doc") do move "%%A" "%DownloadFolder%Documents\" >nul
for %%A in ("%DownloadFolder%*.docx") do move "%%A" "%DownloadFolder%Documents\" >nul
for %%A in ("%DownloadFolder%*.docm") do move "%%A" "%DownloadFolder%Documents\" >nul
for %%A in ("%DownloadFolder%*.pdf") do move "%%A" "%DownloadFolder%Documents\" >nul
for %%A in ("%DownloadFolder%*.rtf") do move "%%A" "%DownloadFolder%Documents\" >nul
for %%A in ("%DownloadFolder%*.xls") do move "%%A" "%DownloadFolder%Documents\" >nul
for %%A in ("%DownloadFolder%*.xlsx") do move "%%A" "%DownloadFolder%Documents\" >nul
for %%A in ("%DownloadFolder%*.xml") do move "%%A" "%DownloadFolder%Documents\" >nul
for %%A in ("%DownloadFolder%*.txt") do move "%%A" "%DownloadFolder%Documents\" >nul
echo ...Sorting epubs..
for %%A in ("%DownloadFolder%*.epub") do move "%%A" "%DownloadFolder%Documents\"
echo ...Sorting pptx..
for %%A in ("%DownloadFolder%*.pptx") do move "%%A" "%DownloadFolder%Documents\" >nul
for %%A in ("%DownloadFolder%*.csv") do move "%%A" "%DownloadFolder%Documents\" >nul
goto :eof

:sortExe

echo Sorting Exe/Installers...
if not exist "%DownloadFolder%Exe\" md "%DownloadFolder%Exe\" >nul
for %%A in ("%DownloadFolder%*.exe") do move "%%A" "%DownloadFolder%Exe\" >nul
for %%A in ("%DownloadFolder%*.msi") do move "%%A" "%DownloadFolder%Exe\" >nul
goto :eof

:sortTd
REM c4d, blend, 3ds, max, obj, fbx

echo Sorting 3D...
if not exist "%DownloadFolder%3D\" md "%DownloadFolder%3D\" >nul
for %%A in ("%DownloadFolder%*.c4d") do move "%%A" "%DownloadFolder%3D\" >nul
for %%A in ("%DownloadFolder%*.blend") do move "%%A" "%DownloadFolder%3D\" >nul
for %%A in ("%DownloadFolder%*.3ds") do move "%%A" "%DownloadFolder%3D\" >nul
for %%A in ("%DownloadFolder%*.max") do move "%%A" "%DownloadFolder%3D\" >nul
for %%A in ("%DownloadFolder%*.obj") do move "%%A" "%DownloadFolder%3D\" >nul
for %%A in ("%DownloadFolder%*.fbx") do move "%%A" "%DownloadFolder%3D\" >nul
goto :eof

:sortOther
echo Sorting Other
if not exist "%DownloadFolder%Other\" md "%DownloadFolder%Other\" >nul
for %%A in ("%DownloadFolder%*.ics") do move "%%A" "%DownloadFolder%Other\" >nul
for %%A in ("%DownloadFolder%\*") do (
    echo move "%%A" "%DownloadFolder%Other\" >nul
    move "%%A" "%DownloadFolder%Other\" >nul
)
for /d %%D in ("%DownloadFolder%\*") do (
    set "FolderName=%%~nD"
    set "MoveThis=1"


    for %%E in (%Excluded%) do (
        if /i "!FolderName!"=="%%E" set "MoveThis=0"
    )
   
    if "!MoveThis!"=="1" (
           echo move "%%D" "%OtherFolder%"
        move "%%D" "%OtherFolder%" >nul
    )
)
goto :eof


:done
echo.
echo Done. All files should be sorted!
echo.
echo If it didn't work how you wanted, right click on "Downloads" folder,
echo click properties, and Previous Versions. You might be able to restore
echo the moves from a previous version.
pause
goto top

Friday, 30 January 2026

Google Docs Appscript GeneralFunctions 2026

 /**

 * Runs automatically when the document is opened.
 */
function onOpen() {
  DocumentApp.getUi()
    .createMenu('TOC Tools')
    .addItem('Update Table of Contents Now', 'updateTOC')
    .addToUi();

  updateTOC();  // Auto-update on open
}


/**
/ ======================================================================================
/ ======================================================================================
/ AK TABLE OF CONTENTS TOOLS!!!!!! CAN RUN ON OPN
/ AK TABLE OF CONTENTS TOOLS!!!!!! CAN RUN ON OPN



function onOpen() {
  DocumentApp.getUi()
    .createMenu('TOC Tools')
    .addItem('Update Table of Contents Now', 'updateTOC')
    .addToUi();

  updateTOC();  // Auto-update on open
}







*/

/**
 * Builds or updates the Table of Contents at the top.
 */
function updateTOC() {
  const doc = DocumentApp.getActiveDocument();
  const body = doc.getBody();

  removeExistingTOC(body);

  const headings = collectHeadings(body);

  // Feedback (assuming you have a showToast function defined)
  if (headings.length === 0) {
    showToast("No headings (Heading 1–3) found. Check paragraph styles.", "TOC Update", 10);
  } else {
    showToast(`Found ${headings.length} headings. TOC updated.`, "TOC Update", 5);
  }

  if (headings.length === 0) return;

  // Insert title (already black and centered)
  // Insert title
    const tocTitle = body.insertParagraph(0, 'Table of Contents');
    tocTitle.setHeading(DocumentApp.ParagraphHeading.HEADING1)
            .setAlignment(DocumentApp.HorizontalAlignment.CENTER)
            .setAttributes({
              BOLD: true,
              FONT_SIZE: 18,
              FOREGROUND_COLOR: '#000000'
            });

    // Set Calibri for the title too
    const titleText = tocTitle.editAsText();
    titleText.setFontFamily(0, titleText.getText().length - 1, 'Calibri');
     
  body.insertParagraph(1, ''); // blank line

  let insertPos = 2;

  headings.forEach(heading => {
    const p = body.insertParagraph(insertPos++, heading.text.trim());
   
    const indent = (heading.level - 1) * 36;
    p.setIndentStart(indent)
    .setIndentEnd(0)
    .setIndentFirstLine(0);

    // Get the text object for fine-grained control
    const textObj = p.editAsText();  // This gives access to the full text range
    const fullLength = textObj.getText().length;

    // Set font family to Calibri for the entire entry
    textObj.setFontFamily(0, fullLength - 1, 'Calibri');

    // Apply black color and NO underline to the entire text
    textObj.setForegroundColor(0, textObj.getText().length - 1, '#000000');
    textObj.setUnderline(0, textObj.getText().length - 1, false);

    // NOW set the link — Docs will try to force blue/underline, but we override after
    textObj.setLinkUrl(0, textObj.getText().length - 1, '#bookmark=' + heading.bookmarkId);

    // Critical override: re-apply styles AFTER setting the link
    textObj.setForegroundColor(0, textObj.getText().length - 1, '#000000');
    textObj.setUnderline(0, textObj.getText().length - 1, false);
  });

  body.insertHorizontalRule(insertPos);
}

function showToast(message, title = "Info", timeoutSeconds = 4) {
  const html = HtmlService.createHtmlOutput(
    `<div style="padding: 12px; font-family: Arial; font-size: 14px;">
       <strong>${title}</strong><br>
       ${message}
     </div>`
  )
  .setWidth(280)
  .setHeight(80);

  const ui = DocumentApp.getUi();
  ui.showModelessDialog(html, " ");  // empty title bar looks cleaner

  // Auto-close after timeout (client-side JS)
  if (timeoutSeconds > 0) {
    html.append(`<script>
      setTimeout(() => google.script.host.close(), ${timeoutSeconds * 1000});
    </script>`);
  }
}




function removeExistingTOC(body) {
  const toRemove = [];
  let foundTitle = false;

  showToast("Starting TOC cleanup scan...", "Debug", 3);

  for (let i = 0; i < body.getNumChildren(); i++) {
    const child = body.getChild(i);

    if (child.getType() !== DocumentApp.ElementType.PARAGRAPH) {
      // Remove HR or other non-paragraph if after title
      if (foundTitle) {
        toRemove.push(i);
      }
      continue;
    }

    const p = child.asParagraph();
    const textTrim = p.getText().trim();
    const textLower = textTrim.toLowerCase();
    const isHeading = p.getHeading() !== DocumentApp.ParagraphHeading.NORMAL_TEXT;

    // Detect title (very forgiving)
    if (!foundTitle && textLower.includes('table') && textLower.includes('contents')) {
      foundTitle = true;
      toRemove.push(i);
      showToast("TOC title found at position " + i, "Debug", 4);
      continue;
    }

    if (foundTitle) {
      // Keep removing while:
      // - blank
      // - OR has link (bookmark link)
      // - OR indented
      // - OR NOT a heading
      const textObj = p.editAsText();
      const hasLink = textObj.getText().length > 0 && textObj.getLinkUrl() !== null;
      const isIndented = p.getIndentStart() > 0;
      const isBlank = textTrim === '';

      if (isBlank || hasLink || isIndented || !isHeading) {
        toRemove.push(i);
        showToast("Removing TOC-like item at " + i + " (link=" + hasLink + ", indent=" + isIndented + ")", "Debug", 2);
      } else {
        // This looks like your first real section heading → STOP and DO NOT remove it
        showToast("Stop at position " + i + " — real heading detected", "Debug", 5);
        break;
      }

      if (toRemove.length > 60) break; // safety
    }
  }

  if (toRemove.length > 0) {
    showToast("Preparing to remove " + toRemove.length + " items", "Info", 4);
    toRemove.sort((a, b) => b - a);
    toRemove.forEach(idx => {
      try {
        body.removeChild(body.getChild(idx));
      } catch (e) {
        showToast("Remove failed at " + idx + ": " + e.message, "Error", 6);
      }
    });
    showToast("Cleanup finished — removed " + toRemove.length + " items", "Success", 5);
  } else {
    showToast("No TOC detected at top — nothing removed", "Warning", 6);
  }
}


/**
 * Collect headings with reliable bookmark handling
 */
/**
 * Collect headings with reliable bookmark handling (no findElement on Body)
 */
function collectHeadings(body) {
  const doc = DocumentApp.getActiveDocument();
  const headings = [];

  for (let i = 0; i < body.getNumChildren(); i++) {
    const child = body.getChild(i);
    if (child.getType() !== DocumentApp.ElementType.PARAGRAPH) continue;

    const p = child.asParagraph();
    const headingType = p.getHeading();

    if (![DocumentApp.ParagraphHeading.HEADING1,
          DocumentApp.ParagraphHeading.HEADING2,
          DocumentApp.ParagraphHeading.HEADING3].includes(headingType)) continue;

    const text = p.getText().trim();
    if (text === '') continue;

    // Get the Text element reliably (most paragraphs have exactly one Text child)
    let textElement = null;
    if (p.getNumChildren() > 0) {
      const potentialText = p.getChild(0);
      if (potentialText.getType() === DocumentApp.ElementType.TEXT) {
        textElement = potentialText.asText();
      }
    }

    // Fallback: If no Text child (very rare for headings), use editAsText() which gives a Text element
    if (!textElement) {
      textElement = p.editAsText();
    }

    // Now create position at offset 0 (start of the text)
    const position = doc.newPosition(textElement, 0);

    const bookmark = doc.addBookmark(position);
    headings.push({
      level: getHeadingLevel(headingType),
      text: text,
      bookmarkId: bookmark.getId()
    });
  }

  return headings;
}
function getHeadingLevel(heading) {
  switch (heading) {
    case DocumentApp.ParagraphHeading.HEADING1: return 1;
    case DocumentApp.ParagraphHeading.HEADING2: return 2;
    case DocumentApp.ParagraphHeading.HEADING3: return 3;
    default: return 0;
  }
}

/**
/ ======================================================================================
/ ======================================================================================
/ END AK TABLE OF CONTENTS TOOLS!!!!!! CAN RUN ON OPN
/ END AK TABLE OF CONTENTS TOOLS!!!!!! CAN RUN ON OPN */


Monday, 3 November 2025

Word VBA Macro create calendars for boox pdf

 Sub CreateMonthlyCalendar_Landscape()

  Dim yearInput As Integer

    Dim monthIndex As Integer

    Dim mName As String

    Dim firstDate As Date

    Dim lastDate As Date

    Dim tableRows As Integer, tableCols As Integer

    Dim doc As Document

    Dim calTable As Table

    Dim r As Integer, c As Integer

    Dim currentDate As Date

    Dim weekendsColor As Long

    Dim firstWeekday As Integer

    Dim totalDays As Integer

    Dim totalSlots As Integer

    Dim numWeeks As Integer

    

    ' === SETTINGS ===

    weekendsColor = RGB(220, 230, 241)  ' Light blue weekend shading

    tableCols = 7

    

    ' === ASK FOR YEAR ===

    yearInput = InputBox("Enter the calendar year (e.g., 2025):", "Calendar Year", year(Date))

    If Not IsNumeric(yearInput) Then Exit Sub

    If yearInput < 1900 Or yearInput > 2100 Then

        MsgBox "Please enter a valid year between 1900 and 2100."

        Exit Sub

    End If

    

    Set doc = Documents.Add

    With doc.PageSetup

        .Orientation = wdOrientLandscape

        .TopMargin = CentimetersToPoints(0.8)

        .BottomMargin = CentimetersToPoints(0.8)

        .LeftMargin = CentimetersToPoints(0.8)

        .RightMargin = CentimetersToPoints(0.8)

    End With

    

    ' === LOOP THROUGH EACH MONTH ===

    For monthIndex = 1 To 12

        mName = monthName(monthIndex)

        firstDate = DateSerial(yearInput, monthIndex, 1)

        lastDate = DateSerial(yearInput, monthIndex + 1, 0)

        

        ' Page break between months

        If monthIndex > 1 Then doc.Range(doc.Content.End - 1).InsertBreak Type:=wdPageBreak

        

        ' Month heading

        doc.Range.InsertAfter vbCr

        With doc.Paragraphs.Last.Range

            .Text = mName & " " & yearInput

            .Style = "Heading 1"

            .ParagraphFormat.Alignment = wdAlignParagraphCenter

            .InsertParagraphAfter

        End With

        

        ' Determine number of rows needed

        firstWeekday = Weekday(firstDate, vbMonday)

        totalDays = Day(lastDate)

        totalSlots = (firstWeekday - 1) + totalDays

        numWeeks = (totalSlots + 6) \ 7

        tableRows = numWeeks + 1  ' +1 for header row

        

        ' Insert table

        Set calTable = doc.Tables.Add(Range:=doc.Paragraphs.Last.Range, NumRows:=tableRows, NumColumns:=tableCols)

        calTable.Borders.Enable = True

        calTable.PreferredWidthType = wdPreferredWidthPercent

        calTable.PreferredWidth = 100

        

        ' Header row (Mon–Sun)

        Dim daysOfWeek As Variant

        daysOfWeek = Array("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun")

        For c = 1 To tableCols

            With calTable.Cell(1, c).Range

                .Text = daysOfWeek(c - 1)

                .ParagraphFormat.Alignment = wdAlignParagraphCenter

                .Bold = True

            End With

        Next c

        

        ' Fill in dates

        currentDate = firstDate

        r = 2

        c = firstWeekday

        

        Do While currentDate <= lastDate

            With calTable.Cell(r, c).Range

                .Text = Day(currentDate)

                .ParagraphFormat.Alignment = wdAlignParagraphRight

            End With

            

            ' Shade weekends

            If Weekday(currentDate, vbMonday) > 5 Then

                calTable.Cell(r, c).Shading.BackgroundPatternColor = weekendsColor

            End If

            

            currentDate = currentDate + 1

            c = c + 1

            If c > 7 Then

                c = 1

                r = r + 1

            End If

        Loop

        

        ' === Adjust table appearance ===

        On Error Resume Next

        calTable.Rows.HeightRule = wdRowHeightExactly

        

        ' Smaller header row

        calTable.Rows(1).Height = CentimetersToPoints(0.9)

        

        ' Slightly smaller date rows

        For r = 2 To calTable.Rows.Count

            calTable.Rows(r).Height = CentimetersToPoints(2.4)

        Next r

        

        ' Column width for landscape fit

        calTable.Columns.PreferredWidth = CentimetersToPoints(3.7)

        On Error GoTo 0

        

        ' Reduce spacing around the table

        calTable.Rows.SpaceBetweenColumns = 0

        calTable.Range.ParagraphFormat.SpaceBefore = 0

        calTable.Range.ParagraphFormat.SpaceAfter = 3

    Next monthIndex

    

    MsgBox "Compact landscape calendar created for " & yearInput & "!", vbInformation

End Sub



Sub AKCreateMonthlyCalendar()


 

    Dim yearInput As Integer

    Dim monthIndex As Integer

    Dim mName As String

    Dim firstDate As Date

    Dim lastDate As Date

    Dim dayNum As Integer

    Dim weekdayNum As Integer

    Dim tableRows As Integer

    Dim tableCols As Integer

    Dim doc As Document

    Dim calTable As Table

    Dim r As Integer, c As Integer

    Dim currentDate As Date

    Dim cellText As String

    Dim weekendsColor As Long

    Dim firstWeekday As Integer

    Dim totalDays As Integer

    Dim totalSlots As Integer

    Dim numWeeks As Integer

    

    ' === SETTINGS ===

    weekendsColor = RGB(220, 230, 241)  ' Light blue highlight for weekends

    tableCols = 7

    

    ' === ASK FOR YEAR ===

    yearInput = InputBox("Enter the calendar year (e.g., 2025):", "Calendar Year", year(Date))

    If Not IsNumeric(yearInput) Then

        MsgBox "Please enter a valid year."

        Exit Sub

    End If

    If yearInput < 1900 Or yearInput > 2100 Then

        MsgBox "Please enter a valid year between 1900 and 2100."

        Exit Sub

    End If

    

    Set doc = Documents.Add

    doc.PageSetup.Orientation = wdOrientPortrait

    

    ' === LOOP THROUGH EACH MONTH ===

    For monthIndex = 1 To 12

        mName = monthName(monthIndex)           ' use a different variable name so MonthName() isn't shadowed

        firstDate = DateSerial(yearInput, monthIndex, 1)

        lastDate = DateSerial(yearInput, monthIndex + 1, 0)

        

        ' Add a page break between months (except first)

        If monthIndex > 1 Then

            doc.Range(doc.Content.End - 1).InsertBreak Type:=wdPageBreak

        End If

        

        ' Insert month heading

        doc.Range.InsertAfter vbCr

        With doc.Paragraphs.Last.Range

            .Text = mName & " " & yearInput

            .Style = "Heading 1"

            .ParagraphFormat.Alignment = wdAlignParagraphCenter

            .InsertParagraphAfter

        End With

        

        ' Calculate number of rows needed for date grid

        firstWeekday = Weekday(firstDate, vbMonday)   ' 1 = Monday ... 7 = Sunday

        totalDays = Day(lastDate)

        totalSlots = (firstWeekday - 1) + totalDays

        numWeeks = (totalSlots + 6) \ 7               ' integer division rounding up

        tableRows = numWeeks + 1                      ' +1 for header row

        

        ' Insert table (header row + weeks rows)

        Set calTable = doc.Tables.Add(Range:=doc.Paragraphs.Last.Range, NumRows:=tableRows, NumColumns:=tableCols)

        calTable.Borders.Enable = True

        calTable.PreferredWidthType = wdPreferredWidthPercent

        calTable.PreferredWidth = 100

        

        ' Header row (Mon–Sun)

        Dim daysOfWeek As Variant

        daysOfWeek = Array("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun")

        For c = 1 To tableCols

            With calTable.Cell(1, c).Range

                .Text = daysOfWeek(c - 1)

                .ParagraphFormat.Alignment = wdAlignParagraphCenter

                .Bold = True

            End With

        Next c

        

        ' Fill in dates

        currentDate = firstDate

        r = 2

        c = firstWeekday

        

        Do While currentDate <= lastDate

            cellText = CStr(Day(currentDate))

            With calTable.Cell(r, c).Range

                .Text = cellText

                .ParagraphFormat.Alignment = wdAlignParagraphRight

                .ParagraphFormat.SpaceAfter = 6

            End With

            

            ' Highlight weekends (Saturday=6, Sunday=7 when Weekday(..., vbMonday))

            If Weekday(currentDate, vbMonday) > 5 Then

                calTable.Cell(r, c).Shading.BackgroundPatternColor = weekendsColor

            End If

            

            ' Advance to next day

            currentDate = currentDate + 1

            c = c + 1

            If c > 7 Then

                c = 1

                r = r + 1

            End If

        Loop

        

        ' Optional: make cells a reasonable size for notes

        On Error Resume Next

        calTable.Rows.HeightRule = wdRowHeightExactly

        calTable.Rows.Height = CentimetersToPoints(2.6)  ' adjust as desired

        calTable.Columns.PreferredWidth = CentimetersToPoints(2.5)

        On Error GoTo 0

        

        calTable.Range.ParagraphFormat.SpaceBefore = 3

        calTable.Range.ParagraphFormat.SpaceAfter = 3

    Next monthIndex

    

    MsgBox "Calendar created for " & yearInput & "!", vbInformation

End Sub